Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 8ce0fe3

Browse files
committed
move away from process.nextTick
There are two problems with process.nextTick. Less severe is that it reduces performance for all async callback from native code. The more severe one is that it causes weird and unpredictable behavior when trying to interop with promises and async/await code. In particular, we have an invariant where we always emit certain events and invoke certain callbacks "asynchronously". However, that currently doesn't apply to Promise, since we "force" asynchronousity throug process.nextTick which occurs before any microtick. Hence, for any promise/micro-tick based code things actually appear to occur synchronously. Refs: #51070 PR: #51114
1 parent fc102f2 commit 8ce0fe3

38 files changed

+162
-127
lines changed

lib/_http_client.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ const {
4343

4444
const net = require('net');
4545
const assert = require('internal/assert');
46+
const { queueMicrotask } = require('internal/process/task_queues');
4647
const {
4748
kEmptyObject,
4849
once,
@@ -342,7 +343,7 @@ function ClientRequest(input, options, cb) {
342343
if (typeof optsWithoutSignal.createConnection === 'function') {
343344
const oncreate = once((err, socket) => {
344345
if (err) {
345-
process.nextTick(() => this.emit('error', err));
346+
queueMicrotask(() => this.emit('error', err));
346347
} else {
347348
this.onSocket(socket);
348349
}
@@ -405,7 +406,7 @@ ClientRequest.prototype.abort = function abort() {
405406
return;
406407
}
407408
this.aborted = true;
408-
process.nextTick(emitAbortNT, this);
409+
queueMicrotask(() => emitAbortNT(this));
409410
this.destroy();
410411
};
411412

@@ -722,11 +723,11 @@ function responseKeepAlive(req) {
722723
// has no 'error' handler.
723724

724725
// There are cases where _handle === null. Avoid those. Passing undefined to
725-
// nextTick() will call getDefaultTriggerAsyncId() to retrieve the id.
726+
// queueMicrotask will call getDefaultTriggerAsyncId() to retrieve the id.
726727
const asyncId = socket._handle ? socket._handle.getAsyncId() : undefined;
727728
// Mark this socket as available, AFTER user-added end
728729
// handlers have a chance to run.
729-
defaultTriggerAsyncIdScope(asyncId, process.nextTick, emitFreeNT, req);
730+
defaultTriggerAsyncIdScope(asyncId, queueMicrotask, emitFreeNT, req);
730731

731732
req.destroyed = true;
732733
if (req.res) {
@@ -860,7 +861,7 @@ function listenSocketTimeout(req) {
860861
ClientRequest.prototype.onSocket = function onSocket(socket, err) {
861862
// TODO(ronag): Between here and onSocketNT the socket
862863
// has no 'error' handler.
863-
process.nextTick(onSocketNT, this, socket, err);
864+
queueMicrotask(() => onSocketNT(this, socket, err));
864865
};
865866

866867
function onSocketNT(req, socket, err) {

lib/_http_incoming.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const {
3131
} = primordials;
3232

3333
const { Readable, finished } = require('stream');
34+
const { queueMicrotask } = require('internal/process/task_queues');
3435

3536
const kHeaders = Symbol('kHeaders');
3637
const kHeadersDistinct = Symbol('kHeadersDistinct');
@@ -236,10 +237,10 @@ IncomingMessage.prototype._destroy = function _destroy(err, cb) {
236237
e = null;
237238
}
238239
cleanup();
239-
process.nextTick(onError, this, e || err, cb);
240+
queueMicrotask(() => (onError, this, e || err, cb));
240241
});
241242
} else {
242-
process.nextTick(onError, this, err, cb);
243+
queueMicrotask(() => onError(this, err, cb));
243244
}
244245
};
245246

lib/_http_outgoing.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ const {
7676
} = require('internal/errors');
7777
const { validateString } = require('internal/validators');
7878
const { isUint8Array } = require('internal/util/types');
79+
const { queueMicrotask } = require('internal/process/task_queues');
7980

8081
let debug = require('internal/util/debuglog').debuglog('http', (fn) => {
8182
debug = fn;
@@ -888,7 +889,7 @@ OutgoingMessage.prototype.write = function write(chunk, encoding, callback) {
888889
function onError(msg, err, callback) {
889890
const triggerAsyncId = msg.socket ? msg.socket[async_id_symbol] : undefined;
890891
defaultTriggerAsyncIdScope(triggerAsyncId,
891-
process.nextTick,
892+
queueMicrotask,
892893
emitErrorNt,
893894
msg,
894895
err,
@@ -935,7 +936,7 @@ function write_(msg, chunk, encoding, callback, fromEnd) {
935936
if (!msg.destroyed) {
936937
onError(msg, err, callback);
937938
} else {
938-
process.nextTick(callback, err);
939+
queueMicrotask(() => callback(err));
939940
}
940941
return false;
941942
}
@@ -969,14 +970,14 @@ function write_(msg, chunk, encoding, callback, fromEnd) {
969970
} else {
970971
debug('This type of response MUST NOT have a body. ' +
971972
'Ignoring write() calls.');
972-
process.nextTick(callback);
973+
queueMicrotask(callback);
973974
return true;
974975
}
975976
}
976977

977978
if (!fromEnd && msg.socket && !msg.socket.writableCorked) {
978979
msg.socket.cork();
979-
process.nextTick(connectionCorkNT, msg.socket);
980+
queueMicrotask(() => connectionCorkNT(msg.socket));
980981
}
981982

982983
let ret;
@@ -1110,7 +1111,7 @@ OutgoingMessage.prototype.end = function end(chunk, encoding, callback) {
11101111
} else if (!this._headerSent || this.writableLength || chunk) {
11111112
this._send('', 'latin1', finish);
11121113
} else {
1113-
process.nextTick(finish);
1114+
queueMicrotask(finish);
11141115
}
11151116

11161117
if (this[kSocket]) {

lib/_http_server.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ const {
3838
const net = require('net');
3939
const EE = require('events');
4040
const assert = require('internal/assert');
41+
const { queueMicrotask } = require('internal/process/task_queues');
4142
const {
4243
parsers,
4344
freeParser,
@@ -994,7 +995,7 @@ function resOnFinish(req, res, socket, state, server) {
994995

995996
res.detachSocket(socket);
996997
clearIncoming(req);
997-
process.nextTick(emitCloseNT, res);
998+
queueMicrotask(() => emitCloseNT(res));
998999

9991000
if (res._last) {
10001001
if (typeof socket.destroySoon === 'function') {

lib/_tls_wrap.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ const { owner_symbol } = require('internal/async_hooks').symbols;
6767
const { isArrayBufferView } = require('internal/util/types');
6868
const { SecureContext: NativeSecureContext } = internalBinding('crypto');
6969
const { ConnResetException, codes } = require('internal/errors');
70+
const { queueMicrotask } = require('internal/process/task_queues');
7071
const {
7172
ERR_INVALID_ARG_TYPE,
7273
ERR_INVALID_ARG_VALUE,
@@ -609,7 +610,7 @@ function TLSSocket(socket, opts) {
609610
}
610611

611612
// Read on next tick so the caller has a chance to setup listeners
612-
process.nextTick(initRead, this, socket);
613+
queueMicrotask(() => initRead(this, socket));
613614
}
614615
ObjectSetPrototypeOf(TLSSocket.prototype, net.Socket.prototype);
615616
ObjectSetPrototypeOf(TLSSocket, net.Socket);
@@ -999,7 +1000,7 @@ TLSSocket.prototype.renegotiate = function(options, callback) {
9991000
this._handle.renegotiate();
10001001
} catch (err) {
10011002
if (callback) {
1002-
process.nextTick(callback, err);
1003+
queueMicrotask(() => callback(err));
10031004
}
10041005
return false;
10051006
}

lib/child_process.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ const {
8686
validateString,
8787
} = require('internal/validators');
8888
const child_process = require('internal/child_process');
89+
const { queueMicrotask } = require('internal/process/task_queues');
8990
const {
9091
getValidStdio,
9192
setupChannel,
@@ -783,7 +784,7 @@ function spawn(file, args, options) {
783784
if (options.signal) {
784785
const signal = options.signal;
785786
if (signal.aborted) {
786-
process.nextTick(onAbortListener);
787+
queueMicrotask(onAbortListener);
787788
} else {
788789
addAbortListener ??= require('events').addAbortListener;
789790
const disposable = addAbortListener(signal, onAbortListener);

lib/dgram.js

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ const { Buffer } = require('buffer');
6363
const { deprecate, guessHandleType, promisify } = require('internal/util');
6464
const { isArrayBufferView } = require('internal/util/types');
6565
const EventEmitter = require('events');
66+
const { queueMicrotask } = require('internal/process/task_queues');
6667
const {
6768
defaultTriggerAsyncIdScope,
6869
symbols: { async_id_symbol, owner_symbol },
@@ -436,7 +437,7 @@ function doConnect(ex, self, ip, address, port, callback) {
436437

437438
if (ex) {
438439
state.connectState = CONNECT_STATE_DISCONNECTED;
439-
return process.nextTick(() => {
440+
return queueMicrotask(() => {
440441
if (callback) {
441442
self.removeListener('connect', callback);
442443
callback(ex);
@@ -447,7 +448,7 @@ function doConnect(ex, self, ip, address, port, callback) {
447448
}
448449

449450
state.connectState = CONNECT_STATE_CONNECTED;
450-
process.nextTick(() => self.emit('connect'));
451+
queueMicrotask(() => self.emit('connect'));
451452
}
452453

453454

@@ -680,11 +681,11 @@ function doSend(ex, self, ip, list, address, port, callback) {
680681

681682
if (ex) {
682683
if (typeof callback === 'function') {
683-
process.nextTick(callback, ex);
684+
queueMicrotask(callback, ex);
684685
return;
685686
}
686687

687-
process.nextTick(() => self.emit('error', ex));
688+
queueMicrotask(() => self.emit('error', ex));
688689
return;
689690
} else if (!state.handle) {
690691
return;
@@ -709,14 +710,14 @@ function doSend(ex, self, ip, list, address, port, callback) {
709710
// Synchronous finish. The return code is msg_length + 1 so that we can
710711
// distinguish between synchronous success and asynchronous success.
711712
if (callback)
712-
process.nextTick(callback, null, err - 1);
713+
queueMicrotask(() => (callback, null, err - 1));
713714
return;
714715
}
715716

716717
if (err && callback) {
717718
// Don't emit as error, dgram_legacy.js compatibility
718719
const ex = new ExceptionWithHostPort(err, 'send', address, port);
719-
process.nextTick(callback, ex);
720+
queueMicrotask(() => callback(ex));
720721
}
721722
}
722723

@@ -747,7 +748,7 @@ Socket.prototype.close = function(callback) {
747748
state.handle.close();
748749
state.handle = null;
749750
defaultTriggerAsyncIdScope(this[async_id_symbol],
750-
process.nextTick,
751+
queueMicrotask,
751752
socketCloseNT,
752753
this);
753754

lib/diagnostics_channel.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const {
2525
const {
2626
validateFunction,
2727
} = require('internal/validators');
28+
const { queueMicrotask } = require('internal/process/task_queues');
2829

2930
const { triggerUncaughtException } = internalBinding('errors');
3031

@@ -82,7 +83,7 @@ function wrapStoreRun(store, data, next, transform = defaultTransform) {
8283
try {
8384
context = transform(data);
8485
} catch (err) {
85-
process.nextTick(() => {
86+
queueMicrotask(() => {
8687
triggerUncaughtException(err, false);
8788
});
8889
return next();
@@ -141,7 +142,7 @@ class ActiveChannel {
141142
const onMessage = this._subscribers[i];
142143
onMessage(data, this.name);
143144
} catch (err) {
144-
process.nextTick(() => {
145+
queueMicrotask(() => {
145146
triggerUncaughtException(err, false);
146147
});
147148
}

lib/dns.js

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ const {
8585
validatePort,
8686
validateString,
8787
} = require('internal/validators');
88+
const { queueMicrotask } = require('internal/process/task_queues');
8889

8990
const {
9091
GetAddrInfoReqWrap,
@@ -194,20 +195,20 @@ function lookup(hostname, options, callback) {
194195
if (!hostname) {
195196
emitInvalidHostnameWarning(hostname);
196197
if (all) {
197-
process.nextTick(callback, null, []);
198+
queueMicrotask(() => callback(null, []));
198199
} else {
199-
process.nextTick(callback, null, null, family === 6 ? 6 : 4);
200+
queueMicrotask(() => callback(null, null, family === 6 ? 6 : 4));
200201
}
201202
return {};
202203
}
203204

204205
const matchedFamily = isIP(hostname);
205206
if (matchedFamily) {
206207
if (all) {
207-
process.nextTick(
208-
callback, null, [{ address: hostname, family: matchedFamily }]);
208+
queueMicrotask(() =>
209+
callback(null, [{ address: hostname, family: matchedFamily }]));
209210
} else {
210-
process.nextTick(callback, null, hostname, matchedFamily);
211+
queueMicrotask(() => callback(null, hostname, matchedFamily));
211212
}
212213
return {};
213214
}
@@ -222,7 +223,8 @@ function lookup(hostname, options, callback) {
222223
req, hostname, family, hints, verbatim,
223224
);
224225
if (err) {
225-
process.nextTick(callback, new DNSException(err, 'getaddrinfo', hostname));
226+
const ex = new DNSException(err, 'getaddrinfo', hostname);
227+
queueMicrotask(() => callback(ex));
226228
return {};
227229
}
228230
if (hasObserver('dns')) {

lib/events.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -375,9 +375,9 @@ function addCatch(that, promise, type, args) {
375375

376376
if (typeof then === 'function') {
377377
then.call(promise, undefined, function(err) {
378-
// The callback is called with nextTick to avoid a follow-up
378+
// The callback is called with queueMicrotask to avoid a follow-up
379379
// rejection from this promise.
380-
process.nextTick(emitUnhandledRejectionOrErr, that, err, type, args);
380+
queueMicrotask(() => emitUnhandledRejectionOrErr(that, err, type, args));
381381
});
382382
}
383383
} catch (err) {

0 commit comments

Comments
 (0)