Skip to content

Commit 460cc62

Browse files
apapirovskiBridgeAR
authored andcommitted
process: code cleanup for nextTick
Fix a few edge cases and non-obvious issues with nextTick: 1. Emit destroy hook in a try-finally rather than triggering it before the callback runs. 2. Re-word comment for processPromiseRejections and make sure it returns true in the rejectionHandled case too. 3. Small readability improvements. PR-URL: #28047 Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: Joyee Cheung <[email protected]> Reviewed-By: Jeremiah Senkpiel <[email protected]> Reviewed-By: Rich Trott <[email protected]>
1 parent d4bb88e commit 460cc62

File tree

3 files changed

+73
-29
lines changed

3 files changed

+73
-29
lines changed

‎lib/internal/process/promises.js‎

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -139,17 +139,19 @@ function emitDeprecationWarning(){
139139
}
140140
}
141141

142-
// If this method returns true, at least one more tick need to be
143-
// scheduled to process any potential pending rejections
142+
// If this method returns true, we've executed user code or triggered
143+
// a warning to be emitted which requires the microtask and next tick
144+
// queues to be drained again.
144145
functionprocessPromiseRejections(){
146+
letmaybeScheduledTicksOrMicrotasks=asyncHandledRejections.length>0;
147+
145148
while(asyncHandledRejections.length>0){
146149
const{ promise, warning }=asyncHandledRejections.shift();
147150
if(!process.emit('rejectionHandled',promise)){
148151
process.emitWarning(warning);
149152
}
150153
}
151154

152-
letmaybeScheduledTicks=false;
153155
letlen=pendingUnhandledRejections.length;
154156
while(len--){
155157
constpromise=pendingUnhandledRejections.shift();
@@ -167,9 +169,10 @@ function processPromiseRejections(){
167169
state===states.warn){
168170
emitWarning(uid,reason);
169171
}
170-
maybeScheduledTicks=true;
172+
maybeScheduledTicksOrMicrotasks=true;
171173
}
172-
returnmaybeScheduledTicks||pendingUnhandledRejections.length!==0;
174+
returnmaybeScheduledTicksOrMicrotasks||
175+
pendingUnhandledRejections.length!==0;
173176
}
174177

175178
functiongetError(name,message){

‎lib/internal/process/task_queues.js‎

Lines changed: 16 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -65,46 +65,38 @@ function processTicksAndRejections(){
6565
while(tock=queue.shift()){
6666
constasyncId=tock[async_id_symbol];
6767
emitBefore(asyncId,tock[trigger_async_id_symbol]);
68-
// emitDestroy() places the async_id_symbol into an asynchronous queue
69-
// that calls the destroy callback in the future. It's called before
70-
// calling tock.callback so destroy will be called even if the callback
71-
// throws an exception that is handled by 'uncaughtException' or a
72-
// domain.
73-
// TODO(trevnorris): This is a bit of a hack. It relies on the fact
74-
// that nextTick() doesn't allow the event loop to proceed, but if
75-
// any async hooks are enabled during the callback's execution then
76-
// this tock's after hook will be called, but not its destroy hook.
77-
if(destroyHooksExist())
78-
emitDestroy(asyncId);
79-
80-
constcallback=tock.callback;
81-
if(tock.args===undefined)
82-
callback();
83-
else
84-
callback(...tock.args);
68+
69+
try{
70+
constcallback=tock.callback;
71+
if(tock.args===undefined)
72+
callback();
73+
else
74+
callback(...tock.args);
75+
}finally{
76+
if(destroyHooksExist())
77+
emitDestroy(asyncId);
78+
}
8579

8680
emitAfter(asyncId);
8781
}
88-
setHasTickScheduled(false);
8982
runMicrotasks();
9083
}while(!queue.isEmpty()||processPromiseRejections());
84+
setHasTickScheduled(false);
9185
setHasRejectionToWarn(false);
9286
}
9387

9488
classTickObject{
95-
constructor(callback,args,triggerAsyncId){
89+
constructor(callback,args){
9690
this.callback=callback;
9791
this.args=args;
9892

9993
constasyncId=newAsyncId();
94+
consttriggerAsyncId=getDefaultTriggerAsyncId();
10095
this[async_id_symbol]=asyncId;
10196
this[trigger_async_id_symbol]=triggerAsyncId;
10297

10398
if(initHooksExist()){
104-
emitInit(asyncId,
105-
'TickObject',
106-
triggerAsyncId,
107-
this);
99+
emitInit(asyncId,'TickObject',triggerAsyncId,this);
108100
}
109101
}
110102
}
@@ -132,7 +124,7 @@ function nextTick(callback){
132124

133125
if(queue.isEmpty())
134126
setHasTickScheduled(true);
135-
queue.push(newTickObject(callback,args,getDefaultTriggerAsyncId()));
127+
queue.push(newTickObject(callback,args));
136128
}
137129

138130
letAsyncResource;
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
'use strict';
2+
constcommon=require('../common');
3+
constassert=require('assert');
4+
constasync_hooks=require('async_hooks');
5+
6+
// Checks that enabling async hooks in a callback actually
7+
// triggers after & destroy as expected.
8+
9+
constfnsToTest=[setTimeout,(cb)=>{
10+
setImmediate(()=>{
11+
cb();
12+
13+
// We need to keep the event loop open for this to actually work
14+
// since destroy hooks are triggered in unrefed Immediates
15+
setImmediate(()=>{
16+
hook.disable();
17+
});
18+
});
19+
},(cb)=>{
20+
process.nextTick(()=>{
21+
cb();
22+
23+
// We need to keep the event loop open for this to actually work
24+
// since destroy hooks are triggered in unrefed Immediates
25+
setImmediate(()=>{
26+
hook.disable();
27+
assert.strictEqual(fnsToTest.length,0);
28+
});
29+
});
30+
}];
31+
32+
consthook=async_hooks.createHook({
33+
before: common.mustNotCall(),
34+
after: common.mustCall(()=>{},3),
35+
destroy: common.mustCall(()=>{
36+
hook.disable();
37+
nextTest();
38+
},3)
39+
});
40+
41+
nextTest();
42+
43+
functionnextTest(){
44+
if(fnsToTest.length>0){
45+
fnsToTest.shift()(common.mustCall(()=>{
46+
hook.enable();
47+
}));
48+
}
49+
}

0 commit comments

Comments
(0)