From 9df2edf92b266396111fffab447cb15aaf5f8664 Mon Sep 17 00:00:00 2001 From: Robert Nagy <ronagy@icloud.com> Date: Sat, 23 Dec 2023 10:58:53 +0100 Subject: [PATCH] lib: run microtasks before ticks This resolve multiple timing issues related to promises and nextTick. As well as resolving zaldo in promise only code, i.e. our current best practice of using process.nextTick will always apply and work. Refs: https://github.com/nodejs/node/issues/51156 Refs: https://github.com/nodejs/node/issues/51156#issuecomment-1864656761 Refs: https://github.com/nodejs/node/pull/51114 Refs: https://github.com/nodejs/node/pull/51070 Refs: https://github.com/nodejs/node/issues/51156 PR-URL: https://github.com/nodejs/node/pull/51267 --- doc/api/cli.md | 14 ++++++++++++++ lib/internal/process/task_queues.js | 13 ++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/doc/api/cli.md b/doc/api/cli.md index c17af97f38b3fd..0d68ba3a60d745 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -787,6 +787,20 @@ Use this flag to generate a blob that can be injected into the Node.js binary to produce a [single executable application][]. See the documentation about [this configuration][`--experimental-sea-config`] for details. + +### `--experimental-task-ordering` + +<!-- YAML +added: REPLACEME +--> + +> Stability: 1 - Experimental + +Enable experimental task ordering. Always drain micro task queue +before running `process.nextTick` to avoid unintuitive behavior +and unexpected logical deadlocks when mixing async callback and +event API's with `Promise`, `async`/`await`` and `queueMicroTask`. + ### `--experimental-shadow-realm` <!-- YAML diff --git a/lib/internal/process/task_queues.js b/lib/internal/process/task_queues.js index bcb5eef841dd00..82768885b5b6ff 100644 --- a/lib/internal/process/task_queues.js +++ b/lib/internal/process/task_queues.js @@ -41,6 +41,7 @@ const { const { AsyncResource } = require('async_hooks'); +let experimentalTaskOrdering; // *Must* match Environment::TickInfo::Fields in src/env.h. const kHasTickScheduled = 0; @@ -65,8 +66,16 @@ function runNextTicks() { } function processTicksAndRejections() { + if (experimentalTaskOrdering === undeifned) { + const { getOptionValue } = require('internal/options'); + experimentalTaskOrdering = getOptionValue('--experimental-task-ordering'); + } + let tock; do { + if (experimentalTaskOrdering) { + runMicrotasks(); + } while ((tock = queue.shift()) !== null) { const asyncId = tock[async_id_symbol]; emitBefore(asyncId, tock[trigger_async_id_symbol], tock); @@ -92,7 +101,9 @@ function processTicksAndRejections() { emitAfter(asyncId); } - runMicrotasks(); + if (!experimentalTaskOrdering) { + runMicrotasks(); + } } while (!queue.isEmpty() || processPromiseRejections()); setHasTickScheduled(false); setHasRejectionToWarn(false);