-
Notifications
You must be signed in to change notification settings - Fork 29.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Remove async_hooks setTrigger API #14238
Comments
👍 |
Eventually I want to, but How we can migrate away from |
I keep forgetting that event handlers are synchronous. |
I just dug around in the code and saw that |
Oh, yeah :) edit: I was actually confusing
If you like. The current use of I don't really have an opinion on this. If we are going with a temporary solution for a hypothetical issue, there are some performance concerns by creating the extra scope. Personally, I don't care.
edit: I don't see any difference between EP |
I'll do it just to be extra safe. Experience teaches us that "temporary" is the most permanent state 🤷♂️ . Just for context
From the EP doc174 // Set the trigger id for the next asynchronous resource that is created. The
175 // value is reset after it's been retrieved. This API is similar to
176 // triggerIdScope() except it's more brittle because if a constructor fails the
177 // then the internal field may not be reset.
178 async_hooks.setInitTriggerId(id); While the implementation:function setInitTriggerId(triggerAsyncId) {
// CHECK(Number.isSafeInteger(triggerAsyncId))
// CHECK(triggerAsyncId > 0)
async_uid_fields[kInitTriggerId] = triggerAsyncId;
} there's no |
Now grepping dgram.js (2 usages found)
29 const setInitTriggerId = require('async_hooks').setInitTriggerId;
458 setInitTriggerId(self[async_id_symbol]);
net.js (5 usages found)
43 const { newUid, setInitTriggerId } = require('async_hooks');
295 setInitTriggerId(this[async_id_symbol]);
909 setInitTriggerId(self[async_id_symbol]);
922 setInitTriggerId(self[async_id_symbol]);
1054 setInitTriggerId(self[async_id_symbol]); So the plot thickens |
The "retrieving" part is in |
Ack. [addition] |
@refack Let's not derail this discussion further. I think we are in complete agreement. This discussion is meant to be about removing The EP contained these two functions, so while we are in agreement I suspect others may disagree with removing them. |
Ack. P.S. I'm writing a PR to do just that, and am "struggling" with those delicate details. I'm posting it for review, since I still have 3 failed tests. #14302 |
// Perform several asynchronous tasks in a single call to
// improve performance.
doBatchJob(arg, res => {
// "res" is an array of pseudo-asynchronous tasks, similar
// timers in that they are batched onto another async resource.
for (const item of res) {
// Set the trigger_id for the next operation to that of the
// corresponding resource.
setInitTriggerId(item.asyncId());
fs.readFile(item.path);
// OR the safer version:
runInAsyncIdScope(item.asyncId(), () => fs.readFile(item.path));
}
}); This is missing all the error handling and such for brevity, but the point comes across. This functionality cannot be removed. Before I continue, I'd like to clarify whether removing these is the actual intent? EDIT: Now see #14328. Will continue the discussion about |
Sorry, I wasn't aware of that terminology. This is what I think // Perform several asynchronous tasks in a single call to
// improve performance.
doBatchJob(arg, res => {
// "res" is an array of pseudo-asynchronous tasks, similar
// timers in that they are batched onto another async resource.
for (const item of res) {
// option 1) item should be a AsyncResource
resource = item;
// option 2) create an AsyncResource
resource = new AsyncResource('BatchJob', item.asyncId());
resource.emitBefore();
// triggerAsyncId() will now be item.asyncId(), making it
// possible to track all resource creations with item.asyncId()
// as the origin.
fs.readFile(item.path);
resource.emitAfter();
resource.emitDestroy();
}
}); |
tl;dr Using @AndreasMadsen I see now that while making changes based on PR review feedback the function I need to point out that using The idea behind
function listen(port, host, callback) {
// NOTE: This is based on future code where the async_id is assigned
// early to the net.Server() instance and is then passed to the C++
// TCPWrap constructor.
// If "host" is provided then a dns lookup must be performed, but the
// lookup is only needed because of the call to listen().
if (typeof host === 'string') {
triggerAsyncIdScope(this[async_id_symbol], () => {
dns.resolve(host, (err, records) => { /* ... */ });
});
return this;
}
// Create the resource and bind it immediately.
this._handle = new TCPWrap(this[async_id_symbol]);
this._handle.bind(port)
// Since resource creation was synchronous the callback could be called
// now, but to preserve the always async nature the callback is called in
// process.nextTick().
triggerAsyncIdScope(this[async_id_symbol], () => {
process.nextTick(callback, this);
});
} The only thing that changes by using Addressing your proposal: Also Addressing your example code: Option (1) and option (2) aren't doing the same thing. My script was incorrect and option (1) is how it should have been done, but I don't get the point of doing option (2). Creating two |
@trevnorris I have spent a day, slept on it, and spend another day trying to find a polite way to respond to what you wrote. I don't think I have succeeded, most of the polite drafts I have made have been imprecise. The truth is, I find your mentality about this discussion insincere. There are two things that make me think this:
This kind of close mindedness makes it impossible to have a discussion. Please at least entertain the possibility that there are alternatives. Otherwise, there is no point for me to stay around.
This in particular but in general your entire description of To respond directly to the parts of your message that I find valuable:
Let's stick to examples that are from the implementer perspective. For node-core itself, we will always be able to use
Is the task in the example not to queue something?
This is false. I would also ask that you are more precise, Consider also the classic pool example: class RequestResource inherits async_hooks.AsyncResource {
constructor(val, triggerId) {
super('RequestResource', triggerId);
this.val = val;
}
}
class PoolResource inherits async_hooks.AsyncResource {
constructor() {
super('PoolResource');
}
query(val, cb) {
// the `triggerAsyncId` parameter in the init hook will be different from
// async_hooks.executionAsyncId()
const requestResource = new RequestResource(val, this.asyncId());
addToQueue(val, function (er, data) {
requestResource.emitBefore();
cb(er, data);
requestResource.emitAfter();
requestResource.emitDestroy();
});
}
}
I agree, option (1) is the best solution. Option (2) has the same
They don't get the same If there is a fundamental question you should answer, I think it is this: Is there a meaningful use case (long stack trace, continuation local storage, domain, performance monitoring, etc.) that can't be solved if we remove both |
My certainty comes from now almost 5 years of contemplating this API, and so yes it will take a solid argument to change my mind.
I thought it was sufficiently evident by the fact that I meticulously made sure all function calls were appended with
Now you're just ignoring my comments. Let's go through the issue section:
Yes it is, which is why I went into detail and dug up the commit pointing to how
Didn't feel the need to respond to this because I don't see an example of the condition that would cause
There's no more need to discuss this. I am 100% okay removing
And finally:
TBH I don't get the issue you're after. Allowing the users to observe differences in handle creation times is an important feature of So yes, I have read everything, and what I surmise is that there's a lack of understanding of why the API exists. This is supported by the fact that the class RequestResource inherits async_hooks.AsyncResource {
constructor(val, triggerId) {
super('RequestResource', triggerId);
this.val = val;
// Here is where and why the API is necessary, because the triggerAsyncId
// argument in the TCPWRAP's init() call must be that of this resource.
triggerAsyncIdScope(this.asyncId(), () => {
this.socket = net.createServer().listen(port);
});
}
} I am in no way disagreeing with your usage of |
You know, I'm not exactly new to this either. We were both there in 2012 (nodejs/node-v0.x-archive#3816, https://github.com/AndreasMadsen/async-hook/tree/58c919ed5986e3707d20b111c038d4c08d42fff9). That being said, I fully realize that you have superior experience but absolute experience I will not accept.
I must have forgotten you used that notation then, thanks for clarifying.
I'm sorry if it appears that way, trust me I have read your comments many times.
It is important that you respond to things you disagree with. Otherwise, it is very hard to have a discussion. This is the sort of behaviour that again makes me think you don't want to discuss any of this, I hope it is not the case.
state.newConnection = function () {
if (state.numConnection >= state.maxConnections) {
// This may do logging causing `initTriggerId` to be called.
state.emit('warnConnectionLimit');
}
// calls initTriggerId
return newConnection();
};
// intended result: creates a new connection with `asyncId` as the `triggerAsyncId`
// actual result: creates a new connection with `executionAsyncId()` as the `triggerAsyncId`
async_hooks.triggerAsyncIdScope(asyncId, state.newConnection);
You have made it clear that
Yes, but if the timing changes then the effect of
I have an alternative in mind, but I'm not sure how this should interface with |
EDIT: This thought is incomplete. Writing a better response now.
Sorry. Worded my comment badly. It's not that I disagreed with the issue. It's because I didn't see what the issue was addressing.
There's a difference between timing and call order. Timing can do whatever it wants, but call ordering shouldn't change. This is what a lot of the tests in
Okay. I may understand the disconnect. If we drop class RequestResource inherits async_hooks.AsyncResource {
constructor(val, triggerId) {
super('RequestResource', triggerId);
this.val = val;
// Here is where and why the API is necessary, because the triggerAsyncId
// argument in the TCPWRAP's init() call must be that of this resource.
triggerAsyncIdScope(this.asyncId(), () => {
this.socket = new CustomSocket();
});
}
}
class CustomSocket {
constructor() {
this._triggerId = async_hooks.triggerAsyncId();
}
} This also safely interacts with the internals since So sure. We can drop |
NOTE: I'll be referring to the id values via their NOTE: Will refer to NOTE: First,
is no longer an issue. function initTriggerAsyncId() {
let taid = async_uid_fields[kInitTriggerAsyncId];
// This condition should never be reached by the user. If the user runs
// initTriggerAsyncId() and this condition is true then there's a bug.
if (taid > 0) {
async_uid_fields[kInitTriggerAsyncId] = 0;
return taid;
}
taid = async_uid_fields[kScopedInitTriggerAsyncId];
return taid > 0 ? taid : async_uid_fields[kCurrentAsyncId];
} This should prevent all possible side effects from reaching the user, but if that still bothers you then it could be broken up as such: function internalInitTriggerAsyncId() {
let taid = async_uid_fields[kInitTriggerAsyncId];
if (taid > 0) {
async_uid_fields[kInitTriggerAsyncId] = 0;
return taid;
}
return initTriggerAsyncId();
}
function initTriggerAsyncId() {
taid = async_uid_fields[kScopedInitTriggerAsyncId];
return taid > 0 ? taid : async_uid_fields[kCurrentAsyncId];
} The point of all this is to allow users to turn any arbitrary object into an async resource and use the From this we can see that by preventing the user from setting Here's a complete example: class PooledResource extends async_hooks.AsyncResource {
constructor() {
super('PooledResource');
this.requestQueue = [];
this.requestQueueLength = 0;
}
makeRequest(query, cb) {
const req = {
query,
cb,
id: genId(),
owner: this,
asyncId: async_hooks.newUid(),
// Taking into account the mentioned change, this call won't have any
// side-effects.
triggerId: async_hooks.initTriggerAsyncId(),
};
this.requestQueue.push(req);
// Send all the requests after this execution scope is complete.
if (++this.requestQueueLength === 1)
process.nextTick(sendQuery, this);
emitInit(req.asyncId, 'PooledResourceRequest', req.triggerId, req);
}
close() {
this.emitDestroy();
}
}
function sendQuery(handle) {
// Send the queries off to C++ to be completed.
internalSendAllQueries(handle.requestQueue, handle, queryComplete);
}
// "res" is an array with the same objects that were added to requestQueue
// except the "data" field was added with the return value of the query.
function queryComplete(handle, res) {
// The PooledResource instance has completed, and it's execution scope wraps
// all of the query callbacks. Similar to how every Timeout is run within a
// TimerWrap.
handle.emitBefore();
for (let entry of res) {
// Each individual query has it's own asyncId that needs to emit before the
// callback has run.
async_hooks.emitBefore(entry.asyncId, entry.triggerId);
entry.cb(entry.data);
async_hooks.emitAfter(entry.asyncId);
async_hooks.emitDestroy(entry.asyncId);
}
handle.emitAfter();
}
let resource = null;
// Using the all powerful 42 for demonstration.
async_hooks.initTriggerAsyncIdScope(42, () => {
// The .listen() call here uses setInitTriggerId() internally, but setting
// kInitTriggerId won't affect the internal call or this scope.
net.createServer().listen(8080);
resource = new PooledResource();
});
assert.ok(resource.triggerAsyncId() === 42);
async_hooks.initTriggerAsyncIdScope(43, () => {
resource.makeRequest('some request', () => {});
});
assert.ok(resource.requestQueue[0].triggerId === 43); To summarize:
|
Not true. Your new implementation appears to solve all the listed issues I initially mentioned. Thus, I think it is an acceptable solution that doesn't course the user or the implementer too much harm. There are issues such as, what if the handle creation was changed from being in That being said, I'm still not convinced that
Let's assume this is true for now. I have thought a lot about how to separate these discussions and I think the Your example is more helpful, but I still think it is a poor illustration of where // CustomResource is an implementation separate from the
// user code that calls async_hooks.initTriggerAsyncIdScope below.
// Thus, the triggerAsyncId parameter in async_hooks.AsyncResource can
// not be used.
class CustomResource extends async_hooks.AsyncResource {
constructor() {
super('CustomResource');
}
}
let resource = null;
async_hooks.initTriggerAsyncIdScope(42, () => {
resource = new CustomResource();
});
assert.ok(resource.triggerAsyncId() === 42); In this case, the user of const scope = new AsyncResource('scope', 42);
scope.emitBefore();
let resource = new CustomResource();
scope.emitAfter();
scope.emitDestroy(); In this case compressed DAG: In the uncompressed DAG consider the path In this example, |
Yup. That's the case.
NOTE: While writing some example code your code in #14238 (comment) clicked. I believe the reason it hadn't until now is because the paradigm was so drastically different from how I'll start with an important use case for class Resource extends aysnc_hooks.AsyncResource {
constructor() {
super('Resource');
setImmediate(() => {
this.runChecks();
});
}
} Following your proposal it would look like so: class Resource extends aysnc_hooks.AsyncResource {
constructor() {
super('Resource');
const ar = new AsyncResource('Resource(?)', this.asyncId());
ar.emitBefore();
setImmediate(() => {
this.runChecks();
});
ar.emitAfter();
ar.emitDestroy();
}
} Assuming I now understand your proposal, here are my issues with this scenario:
So, IMO, the argument basically boils down to wither
Hopefully my points above explain why combining |
Sorry for the late response.
I think you do now. But, I think it very rare that setting
We don't guard against that and I could easily imagine a caching module, where if the requested data is cached it just calls the callback synchronously. This may be bad practice, I'm not even sure it is. But certainly, one should not assume that
Agreed. I think performance should be the primary argument for
We already have that issue, simplest example is promises.
I still diagree with this. Yes the
I don't see how that isn't already the case. Right now,
I agree that
I don't think that is defined anywhere, if what you are saying is how it is ment to work then we should document and validate that. Right now the solution I propose is already possible, so there is nothing that prevents the user from doing it that way.
I disagree, inserting a proxy-node between two other nodes is not a graph-operation that causes an information loss. In fact it only adds information, although I would agree that from a practical point of view that information is redudant. |
No worries. I do the same. :-) I've been trying to address too many points in each post. I'd like to start hammering out fewer, more discrete, points. I'll address two of them here.
They are defined in both the EPS and API documentation:
This is contradictory to the model you're proposing. Would you agree? Second question is easier to explain with a code example: class Resource extends aysnc_hooks.AsyncResource {
constructor() {
super('Resource');
const ar = new AsyncResource('Resource(?)', this.asyncId());
ar.emitBefore();
setImmediate(() => {
this.runChecks();
});
ar.emitAfter();
ar.emitDestroy();
}
}
const timer = setTimeout(() => {
const rez = new Resource();
}, 10);
// The async_hooks init() call specifically for the above setImmediate().
function init(id, type, triggerAsyncId) {
// Correct:
assert(rez.asyncId() === triggerAsyncId);
// Incorrect:
assert(async_hooks.executionAsyncId() === triggerAsyncId);
// What it should be:
assert(async_hooks.executionAsyncId() === timer[async_id_symbol]);
} By using |
That wasn't how I read it, but I do see that it can be read that way. I generally don't see any reason to limit the user this, synchronous callbacks may be a bad design but it certainly isn't rare. I don't think we can expect userland to use unconditional asynchronous callbacks.
Yes. The
What is that definition? The documentation says:
That is still true, the execution context is
I'm not sure what you mean? Obviously it is possible to call
I think it is better to look at the entire graph. I understand that you would like const trigger = new Map();
function init(id, type, triggerAsyncId) {
trigger.set(id, triggerAsyncId);
if (/*specifically for the above setImmediate()*/) {
assert(rez.asyncId() === trigger.get(triggerAsyncId));
assert(timer[async_id_symbol] === trigger.get(trigger.get(triggerAsyncId)));
}
} |
To verify, I'm talking about the From documentation of when
The
The graph data is not the only contributing factor to your argument of how to use
I can see the causality of the generated graph, but this compounds the use case for In short, I understand your arguments and I acknowledge the merits of your proposed API, but it doesn't fit within the context of how the current API is meant to operate or how it's documented. |
I'm on vacation until the 18th, so I won't comment on this again until after the 18th. If you say that is how the documentation should be read then I can't really argue against that. But I will say that I don't read it that strictly. Indeed the strictest interpretation of asynchronous would mean that stacking (before(1), before(2), after(2), after(1)) isn't allowed. In any case, I don't think how the API is meant to be used or how the documentation is supposed to be read are good arguments. What matters is:
|
I don't think dismissing the documentation, or the intent of the API, is a sound direction to take. This comment makes it sound like the documentation was written prior to the API being developed, and not from hundreds of hours of brainstorming, experimentation and trial-and-error, and thus it's of little concern if the API is altered. Before we question how the documentation is "supposed to be read" I will note that the documentation outlines when the hooks will be called in the API overview: // before is called just before the resource's callback is called. It can be
// called 0-N times for handles (e.g. TCPWrap), and will be called exactly 1
// time for requests (e.g. FSReqWrap).
function before(asyncId) { }
// after is called just after the resource's callback has finished.
function after(asyncId) { } Also from official documentation:
It could only be more specific if it instead read "is only called just before the resource's callback is called," but I honestly didn't think that level of specificity was required to see that the hooks are only meant to wrap the callbacks resulting from an asynchronous operation.
This isn't a question of "relaxing the rules". It's a change to the fundamental nature of
I don't see how this is possible being that there's a documented example of a scenario occurring similar to the one you've said "isn't allowed" (assuming that 1 and 2 are the hook ids). |
Sorry, I have taken so much time. I find your argumentation very unproductive so I don't have a lot of motivation for discussing this.
The intent of the
This is just yet another superiority argument, I won't respect it! If your paradigm is correct you should be able to argue for it without referring to the hours spent. Something does not become true just because we have thought long and hard about it, it doesn't even validate the claim. There is a funny anecdote about big-integer multiplication. Since 2000 B.C. mankind has been multiplying multi-digit numbers. For two, two-digit numbers 4 one-digit multiplications were required. It was believed among mathematicians for 4000 years that it was impossible to find a faster method, surely we would otherwise have found it by now. In 1960 Kolmogorov expressed this conjecture like so many others before him. Within a week a student called Karatsuba then proved him wrong with some grade-school algebra. To be clear, I actually think you are correct that the global variable is the best strategy. But I don't feel very certain about.
I would like a more productive discussion. But so far your argument style has been to not comment on my arguments or claim superiority. Neither of which makes anyone get any wiser.
I suppose that is strictly speaking correct. However, as a mear reader of the documentation, it is not the kind of details I'm able to notice. I test a certain timing of events. If the test doesn't throw an error, then I will have to take that timing into consideration when writing my userland code. |
It's also possible to provide wrong information.
Sorry. I'll be more direct in my discussion.
Based on the context in this thread, I assume you represent Karatsuba. Which would make me Kolmogorov? You think very well of yourself.
I've been reading this, then scrolling back and reading our conversation, for about 10 mins. Not sure what to say if this is how you feel. I'll again try to summarize the points to see if we're on the same page. The problem we're trying to solve is propagating the correct ids to 1) the const async_hooks = require('async_hooks');
const print = process._rawDebug;
async_hooks.createHook({
init(id, type, tid) {
print(`init ${type}:`, id, tid,
async_hooks.executionAsyncId(), async_hooks.triggerAsyncId());
},
}).enable();
class A extends async_hooks.AsyncResource {
constructor(name) {
super(name);
print(`${name}:`,
async_hooks.executionAsyncId(), async_hooks.triggerAsyncId());
}
}
const a1 = new A('A1');
const wrap = new async_hooks.AsyncResource('foo', a1.asyncId());
wrap.emitBefore();
setImmediate(() => {
print('sI:', async_hooks.executionAsyncId(), async_hooks.triggerAsyncId());
});
wrap.emitAfter();
wrap.emitDestroy();
// output:
// init A1: 5 1 1 0
// A1: 1 0
// init foo: 6 5 1 0
// init Immediate: 7 6 6 5
// sI: 7 6 Do I understand your proposal correctly? |
Perhaps, this is where we don't align with our thoughts. Could you explain what the wrong information is?
Honestly, I just want self-sustaining closed arguments. As I said, I'm guessing that you are correct but I don't really see any bulletproof arguments, yet. PS: The Kolmogorov-Smiroth test is my favorite statistical tool, so I would rather be Kolmogorov :)
Yes.
When did we have |
Stupid comment on my part. Please ignore it.
Hah, noted. :-)
Problem I see using the API this way:
Sorry. I should have been more specific due to the long duration of this conversation. Originally it was called I'd really like your feedback on the old |
Great. This is an excellent point.
Honestly, it seems rather wrong. The I refuse to believe you could have made something so wrong, so I'm a little confused. In any case, this is what I'm imagining you want to implement: const initTriggerAsyncIdScopeStack = [];
function initTriggerAsyncIdScope(triggerAsyncId, cb) {
initTriggerAsyncIdScopeStack.push(async_uid_fields[kScopedInitTriggerAsyncId])
async_uid_fields[kScopedInitTriggerAsyncId] = triggerAsyncId;
try {
cb();
} finally {
async_uid_fields[kScopedInitTriggerAsyncId] = initTriggerAsyncIdScopeStack.pop();
}
}
function initTriggerAsyncId() {
const triggerAsyncId = async_uid_fields[kScopedInitTriggerAsyncId];
return triggerAsyncId > 0 ? triggerAsyncId : async_uid_fields[kCurrentAsyncId];
}
function setInternalInitTriggerAsyncId(triggerAsyncId) {
async_uid_fields[kInternalInitTriggerAsyncId] = triggerAsyncId;
}
function internalInitTriggerAsyncId() {
const triggerAsyncId = async_uid_fields[kInternalInitTriggerAsyncId];
async_uid_fields[kInternalInitTriggerAsyncId] = 0;
return triggerAsyncId > 0 ? triggerAsyncId : initTriggerAsyncId();
} |
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 have 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
. (this)runInAsyncIdScope
. (Remove async_hooks runInAsyncIdScope API #14328)newUid
,emitInit
,emitBefore
,emitAfter
andemitDestroy
(Remove async_hooks Sensitive/low-level Embedder API #15572).edit: there is some overlap between
emitInit
andsetInitTriggerId
in terms ofinitTriggerId
. Hopefully, that won't be an issue.As I personally believe
setInitTriggerId
andtriggerIdScope
are the most questionable, I would like to discuss those first.Background
async_hooks
uses a global state calledasync_uid_fields[kInitTriggerId]
to set thetriggerAsyncId
of the soon to be constructed handle object [1] [2]. It has been mentioned eariler by @trevnorris that a better solution is wanted.To set the
async_uid_fields[kInitTriggerId]
field from JavaScript there are two methodssetInitTriggerId
andtriggerIdScope
.setInitTriggerId
sets theasync_uid_fields[kInitTriggerId]
directly and is used in a single case in node-core. IfinitTriggerId
is not called after when creating the handle object this will have unintended side-effects for the next handle object.triggerIdScope
is a more safe version ofsetInitTriggerId
that creates a temporary scope, outside said scope theasync_uid_fields[kInitTriggerId]
value is not modified. This is not used anywere in node-core.Note: This issue is about removing
setInitTriggerId
andtriggerIdScope
as a public API. Not removingasync_uid_fields[kInitTriggerId]
itself, that may come at later time.Issues
setInitTriggerId
is extremely sensitive and wrong use can have unintended side-effects. Consider:In the above example
setInitTriggerId
will have an unintended side-effect whenstate.freeConnections
istrue
. The implementation is obviously wrong but in a real world case I don't think this will be obvious.triggerIdScope
is a safer option, but even that may fail if more than one handle is created depending on a condition.initTriggerId
is called because the handle logic in node-core is so complex. For example, when debugging withconsole.log
a new handle can be created causinginitTriggerId
to be called.Such side-effect may be obvious for us, but I don't want to tell the user that
console.log
can have unintended side-effects.Note, in all of these cases the handle creation and setting the
initTriggerId
may happen in two separate modules.Solution
triggerIdScope
setInitTriggerId
torequire('internal/async_hooks.js')
triggerAsyncId
directly inAsyncResource
oremitInit
. The API already allows for this.Note: We may want to just deprecate the API in node 8 and remove it in a future version.
/cc @nodejs/async_hooks
The text was updated successfully, but these errors were encountered: