Skip to content

Commit

Permalink
process: add deferTick
Browse files Browse the repository at this point in the history
Adds a new scheduling primitive to resolve zaldo when mixing
traditional Node async programming with async/await and Promises.

We cannot "fix" nextTick without breaking the whole ecosystem.
nextTick usage should be discouraged and we should try to
incrementally move to this new primitive.

TODO:
- [] Fill in concrete examples
- [] Add tests
- [] Add benchmarks
- [] Do we need async hook logic or not?
- [] process._exiting?
- [] Anything else we are unhappy with in regard to nextTick which
     we want to fix?
  • Loading branch information
ronag committed Jan 15, 2024
1 parent 94f824a commit bca84c2
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 1 deletion.
19 changes: 19 additions & 0 deletions doc/api/process.md
Original file line number Diff line number Diff line change
Expand Up @@ -1219,6 +1219,25 @@ const process = require('node:process');
process.debugPort = 5858;
```

## `process.deferTick(callback[, ...args])`

<!-- YAML
added: REPLACEME
-->

* `callback` {Function}
* `...args` {any} Additional arguments to pass when invoking the `callback`

`process.deferTick()` adds `callback` to the "defer tick queue". This queue is
fully drained after the current operation on the JavaScript stack runs to
completion and before the event loop is allowed to continue. It's possible to
create an infinite loop if one were to recursively call `process.deferTick()`.
See the [Event Loop][] guide for more background.

Unlike `process.nextTick`, `process.deferTick()` will run after the "next tick
queue" and the microtask queue has been fully drained as to avoid zaldo when
combinding traditional node asynchronous code with Promises.

## `process.disconnect()`

<!-- YAML
Expand Down
3 changes: 2 additions & 1 deletion lib/internal/bootstrap/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -303,8 +303,9 @@ process.emitWarning = emitWarning;
// bootstrap to make sure that any operation done before this are synchronous.
// If any ticks or timers are scheduled before this they are unlikely to work.
{
const { nextTick, runNextTicks } = setupTaskQueue();
const { nextTick, runNextTicks, deferTick } = setupTaskQueue();
process.nextTick = nextTick;
process.deferTick = deferTick;
// Used to emulate a tick manually in the JS land.
// A better name for this function would be `runNextTicks` but
// it has been exposed to the process object so we keep this legacy name
Expand Down
31 changes: 31 additions & 0 deletions lib/internal/process/task_queues.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ function setHasTickScheduled(value) {
}

const queue = new FixedQueue();
const deferQueue = new FixedQueue();

// Should be in sync with RunNextTicksNative in node_task_queue.cc
function runNextTicks() {
Expand Down Expand Up @@ -93,6 +94,10 @@ function processTicksAndRejections() {
emitAfter(asyncId);
}
runMicrotasks();

let tmp = queue;
queue = deferQueue;
deferQueue = tmp;
} while (!queue.isEmpty() || processPromiseRejections());
setHasTickScheduled(false);
setHasRejectionToWarn(false);
Expand Down Expand Up @@ -133,6 +138,31 @@ function nextTick(callback) {
queue.push(tickObject);
}

function deferTick(callback, ...args) {
validateFunction(callback, 'callback');

if (process._exiting)
return;

if (tickInfo[kHasTickScheduled] === 0) {
tickInfo[kHasTickScheduled] = 1;
}

const asyncId = newAsyncId();
const triggerAsyncId = getDefaultTriggerAsyncId();
const tickObject = {
[async_id_symbol]: asyncId,
[trigger_async_id_symbol]: triggerAsyncId,
callback,
args,
};

if (initHooksExist())
emitInit(asyncId, 'TickObject', triggerAsyncId, tickObject);

deferQueue.push(tickObject);
}

function runMicrotask() {
this.runInAsyncScope(() => {
const callback = this.callback;
Expand Down Expand Up @@ -166,6 +196,7 @@ module.exports = {
setTickCallback(processTicksAndRejections);
return {
nextTick,
deferTick,
runNextTicks,
};
},
Expand Down

0 comments on commit bca84c2

Please sign in to comment.