Skip to content

Commit

Permalink
fixup! Add async context tracking documentation to jsg/README.md
Browse files Browse the repository at this point in the history
  • Loading branch information
jasnell committed Dec 12, 2022
1 parent a746e95 commit f1c48ed
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 3 deletions.
34 changes: 31 additions & 3 deletions src/workerd/api/node/async-hooks.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,37 @@ class AsyncLocalStorage final: public jsg::Object {


class AsyncResource final: public jsg::Object, public jsg::AsyncResource {
// The AsyncResource API allows applications to define their own Async contexts.
// For instance, if user code is using custom thenables instead of native promises,
// or user code wishes to attach event listeners to async contexts.
// The AsyncResource class is an object that user code can use to define its own
// async resources for the purpose of storage context propagation. For instance,
// lets imagine that we have an EventTarget and we want to register two event listeners
// on it that will share the same AsyncLocalStorage context. We can use AsyncResource
// to easily define the context and bind multiple event handler functions to it:
//
// const als = new AsyncLocalStorage();
// const context = als.run(123, () => new AsyncResource('foo'));
// const target = new EventTarget();
// target.addEventListener('abc', context.bind(() => console.log(als.getStore())));
// target.addEventListener('xyz', context.bind(() => console.log(als.getStore())));
// target.addEventListener('bar', () => console.log(als.getStore()));
//
// When the 'abc' and 'xyz' events are emitted, their event handlers will print 123
// to the console. When the 'bar' event is emitted, undefined will be printed.
//
// Alternatively, we can use EventTarget's object event handler:
//
// const als = new AsyncLocalStorage();
//
// class MyHandler extends AsyncResource {
// constructor() { super('foo'); }
// void handleEvent() {
// this.runInAsyncScope(() => console.log(als.getStore()));
// }
// }
//
// const handler = als.run(123, () => new MyHandler());
// const target = new EventTarget();
// target.addEventListener('abc', handler);
// target.addEventListener('xyz', handler);
public:
struct Options {
jsg::Optional<uint64_t> triggerAsyncId;
Expand Down
34 changes: 34 additions & 0 deletions src/workerd/jsg/async-context.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,40 @@ class AsyncResource {
// this will be provided by V8 assuming that the AsyncContext proposal advances through
// TC-39. For now, however, we implement a model that is very similar to that implemented
// by Node.js.
//
// The term "resource" here comes from Node.js, which really doesn't take the time to
// define it properly. Conceptually, an "async resource" is some Thing that generates
// asynchronous activity over time. For instance, a timer is an async resource that
// invokes a callback after a certain period of time elapses; a promise is an async
// resource that may trigger scheduling of a microtask at some point in the future,
// and so forth. Whether or not "resource" is the best term to use to describe these,
// it's what we have because our intent here is to stay aligned with Node.js' model
// as closely as possible.
//
// An async resource has an "execution context" or "execution scope". We enter the
// execution scope immediately before the async resource performs whatever action
// it is going to perform (e.g. invoking a callback), and exit the execution scope
// immediately after.
//
// Execution scopes form a stack. The default execution scope is the Root (which
// we label as id = 0). When we enter the execution scope of a different async resource,
// we push it onto the stack, perform whatever task it is, then pop it back off the
// stack. The Root is associated with the Isolate itself such that every isolate
// always has at least one async resource on the stack at all times.
//
// Every async resource has a storage context. Whatever async resource is currently
// at the top of the stack determines the currently active storage context. So, for
// instance, when we start executing, the Root async resource's storage context is
// active. When a timeout elapses and a timer is going to fire, we enter the timers
// execution scope which makes the timers storage context active. Once the timer
// callback has completed, we return back to the Root async resource's execution
// scope and storage context.
//
// All async resources (except for the Root) are created within the scope of a
// parent, which by default is whichever async resource is at the top of the stack
// when the new resource is created.
//
// When the new resource is created, it inherits the storage context of the parent.
public:
const uint64_t id;
const kj::Maybe<uint64_t> parentId;
Expand Down

0 comments on commit f1c48ed

Please sign in to comment.