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);