You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
As I mentioned a few months ago I'm not comfortable exposing the low-level async_hooks JS API. In the documentation PR #12953 we agreed to keep the low-level JS API undocumented. Some time has now passed and I think we are in a better position to discuss this.
The low-level JS API is quite large, thus I would like to separate the discussion into:
Note: There is some overlap between emitInit and setInitTriggerId in terms of initTriggerId. Hopefully, that won't be an issue.
Background
For implementers the JavaScript Embedding API is used to emit the init, before, after and destroy events. There are currently two APIs for
doing this.
The high-level AsyncResource class API.
The low-level newUid, emitInit, emitBefore, emitAfter, emitDestroy. Sometimes this is called the Sensitive Embedder API, but sometimes that also captures triggerAsyncIdScope(asyncId, cb) which is a very different discussion. Thus I will refer to it as low-level JavaScript Embedder API.
This discussion is just about the newUid, emitInit, emitBefore, emitAfter, emitDestroy API.
A common purpose for the Embedder API is to allow queued jobs to not lose the causal context. An example of this is setTimeout (shown implementation is only conceptual).
const{ newUid, initTriggerId, emitInit,
emitBefore, emitAfter, emitDestroy }=require('async_hooks');functionTimeout(timeout){this.timeout=timeout;this.ontimeout=null;}functionsetTimeout(callback,timeout){consthandle=newTimeout(timeout);constasyncId=newUid();consttriggerAsyncId=initTriggerId();emitInit(asyncId,'Timeout',triggerAsyncId,handle);handle.ontimeout=function(){emitBefore(asyncId,triggerAsyncId);callback();emitAfter(asyncId);emitDestroy(asyncId);};// This may emit an init event if a new request is created, but it can also// add the timeout to an existing request. When the request is completed// it will emit before and after around all the callbacks.// The operation will typically be implemented in C++.// function done(callbackList) {// emitBefore(...)// callbackList.forEach((callback) => callback());// emitAfter(...)// emitDestroy(...)// }addToQueue(handle);}
Alternatively, this can be implemented using the high-level AsyncResource
class.
To make it easier to add the embedding calls to existing code.
To solve an important issue with multiple inheritance, where the Timeout class would already inherit from
a different class, that the implementer doesn't control.
Issues
The issues with the low-level JavaScript Embedder API are:
emitInit can be called twice with the same async_id.
emitDestroy can be called twice with the same async_id. Also possible
with AsyncResource, but we could quite easily fix that.
The triggerAsyncId in emitBefore can be different than in emitInit. Definetly the biggest issue, since it is rather confusing that triggerAsyncId
can be null in emitInit but not in emitBefore.
It is possible to hijack an existing async_id and emit before and after. Might be very theoretical.
Okay, so the problems are perhaps not super serious. But I would like to argue that the reasons for the low-level JavaScript Embedder API are not valid, as one can easily use AsyncResource in all cases.
In general restricting the public API to just AsyncResource will give us much more flexibility in state validation. And I think it will be prone to much fewer errors.
Solution
The simplest solution is to create a separate handle object that just
points to the actual handle object.
The latter solution would require that we use ES5 type classes. Alternatively
we could add a static emitInit method to AsyncResource that can act as a constructor.
classAsyncResource{constructor(name){AsyncResource.emitInit(this,name);}staticemitInit(self,name){// current constructor code}emitBefore(){AsyncResource.emitBefore(this);}staticemitBefore(self){// current AsyncResource.prototype.emitBefore code}emitAfter(){AsyncResource.emitAfter(this);}staticemitAfter(self){// current AsyncResource.prototype.emitAfter code}emitDestroy(){AsyncResource.emitDestroy(this);}staticemitDestroy(self){// current AsyncResource.prototype.emitDestroy code}// ...}
/cc @nodejs/async_hooks
The text was updated successfully, but these errors were encountered:
As I mentioned a few months ago I'm not comfortable exposing the low-level async_hooks JS API. In the documentation PR #12953 we agreed to keep the low-level JS API undocumented. Some time has now passed and I think we are in a better position to discuss this.
The low-level JS API is quite large, thus I would like to separate the discussion into:
setInitTriggerId
andtriggerIdScope
. (Remove async_hooks setTrigger API #14238)runInAsyncIdScope
. (Remove async_hooks runInAsyncIdScope API #14328)newUid
,emitInit
,emitBefore
,emitAfter
andemitDestroy
. (this)Note: There is some overlap between
emitInit
andsetInitTriggerId
in terms ofinitTriggerId
. Hopefully, that won't be an issue.Background
For implementers the JavaScript Embedding API is used to emit the
init
,before
,after
anddestroy
events. There are currently two APIs fordoing this.
AsyncResource
class API.newUid
,emitInit
,emitBefore
,emitAfter
,emitDestroy
. Sometimes this is called the Sensitive Embedder API, but sometimes that also capturestriggerAsyncIdScope(asyncId, cb)
which is a very different discussion. Thus I will refer to it as low-level JavaScript Embedder API.This discussion is just about the
newUid
,emitInit
,emitBefore
,emitAfter
,emitDestroy
API.A common purpose for the Embedder API is to allow queued jobs to not lose the causal context. An example of this is
setTimeout
(shown implementation is only conceptual).Alternatively, this can be implemented using the high-level
AsyncResource
class.
The low-level JavaScript Embedder API exists to:
multiple inheritance, where the
Timeout
class would already inherit froma different class, that the implementer doesn't control.
Issues
The issues with the low-level JavaScript Embedder API are:
emitInit
can be called twice with the sameasync_id
.emitDestroy
can be called twice with the sameasync_id
. Also possiblewith
AsyncResource
, but we could quite easily fix that.triggerAsyncId
inemitBefore
can be different than inemitInit
.Definetly the biggest issue, since it is rather confusing that
triggerAsyncId
can be null in
emitInit
but not inemitBefore
.async_id
and emit before and after.Might be very theoretical.
Okay, so the problems are perhaps not super serious. But I would like to argue that the reasons for the low-level JavaScript Embedder API are not valid, as one can easily use
AsyncResource
in all cases.In general restricting the public API to just
AsyncResource
will give us much more flexibility in state validation. And I think it will be prone to much fewer errors.Solution
The simplest solution is to create a separate
handle
object that justpoints to the actual handle object.
If creating of the extra resource object is somehow inconvenient, it is
possible to call the AsyncResource methods directly on the handle object.
The latter solution would require that we use ES5 type classes. Alternatively
we could add a
static emitInit
method toAsyncResource
that can act as a constructor.Our
AsyncResource
implementation would just be:/cc @nodejs/async_hooks
The text was updated successfully, but these errors were encountered: