Skip to content

Commit 8c2f0a8

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
1 parent fc102f2 commit 8c2f0a8

38 files changed

+128
-127
lines changed

lib/_http_client.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,7 @@ function ClientRequest(input, options, cb) {
342342
if (typeof optsWithoutSignal.createConnection === 'function') {
343343
const oncreate = once((err, socket) => {
344344
if (err) {
345-
process.nextTick(() => this.emit('error', err));
345+
queueMicrotask(() => this.emit('error', err));
346346
} else {
347347
this.onSocket(socket);
348348
}
@@ -405,7 +405,7 @@ ClientRequest.prototype.abort = function abort() {
405405
return;
406406
}
407407
this.aborted = true;
408-
process.nextTick(emitAbortNT, this);
408+
queueMicrotask(() => emitAbortNT(this));
409409
this.destroy();
410410
};
411411

@@ -722,11 +722,11 @@ function responseKeepAlive(req) {
722722
// has no 'error' handler.
723723

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

731731
req.destroyed = true;
732732
if (req.res) {
@@ -860,7 +860,7 @@ function listenSocketTimeout(req) {
860860
ClientRequest.prototype.onSocket = function onSocket(socket, err) {
861861
// TODO(ronag): Between here and onSocketNT the socket
862862
// has no 'error' handler.
863-
process.nextTick(onSocketNT, this, socket, err);
863+
queueMicrotask(() => onSocketNT(this, socket, err));
864864
};
865865

866866
function onSocketNT(req, socket, err) {

lib/_http_incoming.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,10 +236,10 @@ IncomingMessage.prototype._destroy = function _destroy(err, cb) {
236236
e = null;
237237
}
238238
cleanup();
239-
process.nextTick(onError, this, e || err, cb);
239+
queueMicrotask(() => (onError, this, e || err, cb));
240240
});
241241
} else {
242-
process.nextTick(onError, this, err, cb);
242+
queueMicrotask(() => onError(this, err, cb));
243243
}
244244
};
245245

lib/_http_outgoing.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -888,7 +888,7 @@ OutgoingMessage.prototype.write = function write(chunk, encoding, callback) {
888888
function onError(msg, err, callback) {
889889
const triggerAsyncId = msg.socket ? msg.socket[async_id_symbol] : undefined;
890890
defaultTriggerAsyncIdScope(triggerAsyncId,
891-
process.nextTick,
891+
queueMicrotask,
892892
emitErrorNt,
893893
msg,
894894
err,
@@ -935,7 +935,7 @@ function write_(msg, chunk, encoding, callback, fromEnd) {
935935
if (!msg.destroyed) {
936936
onError(msg, err, callback);
937937
} else {
938-
process.nextTick(callback, err);
938+
queueMicrotask(() => callback(err));
939939
}
940940
return false;
941941
}
@@ -969,14 +969,14 @@ function write_(msg, chunk, encoding, callback, fromEnd) {
969969
} else {
970970
debug('This type of response MUST NOT have a body. ' +
971971
'Ignoring write() calls.');
972-
process.nextTick(callback);
972+
queueMicrotask(callback);
973973
return true;
974974
}
975975
}
976976

977977
if (!fromEnd && msg.socket && !msg.socket.writableCorked) {
978978
msg.socket.cork();
979-
process.nextTick(connectionCorkNT, msg.socket);
979+
queueMicrotask(() => connectionCorkNT(msg.socket));
980980
}
981981

982982
let ret;
@@ -1110,7 +1110,7 @@ OutgoingMessage.prototype.end = function end(chunk, encoding, callback) {
11101110
} else if (!this._headerSent || this.writableLength || chunk) {
11111111
this._send('', 'latin1', finish);
11121112
} else {
1113-
process.nextTick(finish);
1113+
queueMicrotask(finish);
11141114
}
11151115

11161116
if (this[kSocket]) {

lib/_http_server.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -994,7 +994,7 @@ function resOnFinish(req, res, socket, state, server) {
994994

995995
res.detachSocket(socket);
996996
clearIncoming(req);
997-
process.nextTick(emitCloseNT, res);
997+
queueMicrotask(() => emitCloseNT(res));
998998

999999
if (res._last) {
10001000
if (typeof socket.destroySoon === 'function') {

lib/_tls_wrap.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -609,7 +609,7 @@ function TLSSocket(socket, opts) {
609609
}
610610

611611
// Read on next tick so the caller has a chance to setup listeners
612-
process.nextTick(initRead, this, socket);
612+
queueMicrotask(() => initRead(this, socket));
613613
}
614614
ObjectSetPrototypeOf(TLSSocket.prototype, net.Socket.prototype);
615615
ObjectSetPrototypeOf(TLSSocket, net.Socket);
@@ -999,7 +999,7 @@ TLSSocket.prototype.renegotiate = function(options, callback) {
999999
this._handle.renegotiate();
10001000
} catch (err) {
10011001
if (callback) {
1002-
process.nextTick(callback, err);
1002+
queueMicrotask(() => callback(err));
10031003
}
10041004
return false;
10051005
}

lib/child_process.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -783,7 +783,7 @@ function spawn(file, args, options) {
783783
if (options.signal) {
784784
const signal = options.signal;
785785
if (signal.aborted) {
786-
process.nextTick(onAbortListener);
786+
queueMicrotask(onAbortListener);
787787
} else {
788788
addAbortListener ??= require('events').addAbortListener;
789789
const disposable = addAbortListener(signal, onAbortListener);

lib/dgram.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,7 @@ function doConnect(ex, self, ip, address, port, callback) {
436436

437437
if (ex) {
438438
state.connectState = CONNECT_STATE_DISCONNECTED;
439-
return process.nextTick(() => {
439+
return queueMicrotask(() => {
440440
if (callback) {
441441
self.removeListener('connect', callback);
442442
callback(ex);
@@ -447,7 +447,7 @@ function doConnect(ex, self, ip, address, port, callback) {
447447
}
448448

449449
state.connectState = CONNECT_STATE_CONNECTED;
450-
process.nextTick(() => self.emit('connect'));
450+
queueMicrotask(() => self.emit('connect'));
451451
}
452452

453453

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

681681
if (ex) {
682682
if (typeof callback === 'function') {
683-
process.nextTick(callback, ex);
683+
queueMicrotask(callback, ex);
684684
return;
685685
}
686686

687-
process.nextTick(() => self.emit('error', ex));
687+
queueMicrotask(() => self.emit('error', ex));
688688
return;
689689
} else if (!state.handle) {
690690
return;
@@ -709,14 +709,14 @@ function doSend(ex, self, ip, list, address, port, callback) {
709709
// Synchronous finish. The return code is msg_length + 1 so that we can
710710
// distinguish between synchronous success and asynchronous success.
711711
if (callback)
712-
process.nextTick(callback, null, err - 1);
712+
queueMicrotask(() => (callback, null, err - 1));
713713
return;
714714
}
715715

716716
if (err && callback) {
717717
// Don't emit as error, dgram_legacy.js compatibility
718718
const ex = new ExceptionWithHostPort(err, 'send', address, port);
719-
process.nextTick(callback, ex);
719+
queueMicrotask(() => callback(ex));
720720
}
721721
}
722722

@@ -747,7 +747,7 @@ Socket.prototype.close = function(callback) {
747747
state.handle.close();
748748
state.handle = null;
749749
defaultTriggerAsyncIdScope(this[async_id_symbol],
750-
process.nextTick,
750+
queueMicrotask,
751751
socketCloseNT,
752752
this);
753753

lib/diagnostics_channel.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ function wrapStoreRun(store, data, next, transform = defaultTransform) {
8282
try {
8383
context = transform(data);
8484
} catch (err) {
85-
process.nextTick(() => {
85+
queueMicrotask(() => {
8686
triggerUncaughtException(err, false);
8787
});
8888
return next();
@@ -141,7 +141,7 @@ class ActiveChannel {
141141
const onMessage = this._subscribers[i];
142142
onMessage(data, this.name);
143143
} catch (err) {
144-
process.nextTick(() => {
144+
queueMicrotask(() => {
145145
triggerUncaughtException(err, false);
146146
});
147147
}

lib/dns.js

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -194,20 +194,20 @@ function lookup(hostname, options, callback) {
194194
if (!hostname) {
195195
emitInvalidHostnameWarning(hostname);
196196
if (all) {
197-
process.nextTick(callback, null, []);
197+
queueMicrotask(() => callback(null, []));
198198
} else {
199-
process.nextTick(callback, null, null, family === 6 ? 6 : 4);
199+
queueMicrotask(() => callback(null, null, family === 6 ? 6 : 4));
200200
}
201201
return {};
202202
}
203203

204204
const matchedFamily = isIP(hostname);
205205
if (matchedFamily) {
206206
if (all) {
207-
process.nextTick(
208-
callback, null, [{ address: hostname, family: matchedFamily }]);
207+
queueMicrotask(() =>
208+
callback(null, [{ address: hostname, family: matchedFamily }]));
209209
} else {
210-
process.nextTick(callback, null, hostname, matchedFamily);
210+
queueMicrotask(() => callback(null, hostname, matchedFamily));
211211
}
212212
return {};
213213
}
@@ -222,7 +222,8 @@ function lookup(hostname, options, callback) {
222222
req, hostname, family, hints, verbatim,
223223
);
224224
if (err) {
225-
process.nextTick(callback, new DNSException(err, 'getaddrinfo', hostname));
225+
const ex = new DNSException(err, 'getaddrinfo', hostname);
226+
queueMicrotask(() => callback(ex));
226227
return {};
227228
}
228229
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)