aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2023-09-05 16:52:57 -0800
committerGravatar GitHub <noreply@github.com> 2023-09-05 17:52:57 -0700
commit1bd5b245b8a55353e60a2decad507ef8014be044 (patch)
tree1a5cd5bcc7d7758bbfd154cf49470c1b0f3dc1bb
parentacfd028e8f859a0e8139b7adab5d319e326c2373 (diff)
downloadbun-1bd5b245b8a55353e60a2decad507ef8014be044.tar.gz
bun-1bd5b245b8a55353e60a2decad507ef8014be044.tar.zst
bun-1bd5b245b8a55353e60a2decad507ef8014be044.zip
Align `process.nextTick` execution order with Node (#4409)
* Align `process.nextTick` execution order with Node * some tests * formatting * fixups * fix the test failures * simplify the logic here * push it up --------- Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Co-authored-by: dave caruso <me@paperdave.net>
-rw-r--r--src/bun.js/bindings/JSNextTickQueue.cpp97
-rw-r--r--src/bun.js/bindings/JSNextTickQueue.h41
-rw-r--r--src/bun.js/bindings/Process.cpp98
-rw-r--r--src/bun.js/bindings/Process.lut.h2
-rw-r--r--src/bun.js/bindings/ZigGlobalObject.cpp124
-rw-r--r--src/bun.js/bindings/ZigGlobalObject.h5
-rw-r--r--src/bun.js/bindings/bindings.cpp3
-rw-r--r--src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h1
-rw-r--r--src/bun.js/bindings/webcore/DOMIsoSubspaces.h2
-rw-r--r--src/bun.js/event_loop.zig15
-rw-r--r--src/bun_js.zig2
-rw-r--r--src/js/builtins/ProcessObjectInternals.ts202
-rw-r--r--src/js/out/WebCoreJSBuiltins.cpp8
-rw-r--r--src/js/out/WebCoreJSBuiltins.h11
-rw-r--r--test/js/bun/spawn/exit-code.test.ts5
-rw-r--r--test/js/node/process/process-nexttick.test.js950
-rw-r--r--test/js/node/readline/readline_promises.node.test.ts2
17 files changed, 1481 insertions, 87 deletions
diff --git a/src/bun.js/bindings/JSNextTickQueue.cpp b/src/bun.js/bindings/JSNextTickQueue.cpp
new file mode 100644
index 000000000..8916ef6c8
--- /dev/null
+++ b/src/bun.js/bindings/JSNextTickQueue.cpp
@@ -0,0 +1,97 @@
+#include "root.h"
+
+#include "JavaScriptCore/JSCJSValueInlines.h"
+#include "JavaScriptCore/JSInternalPromise.h"
+#include "JavaScriptCore/LazyPropertyInlines.h"
+#include <JavaScriptCore/Weak.h>
+#include <JavaScriptCore/GetterSetter.h>
+
+#include "JSNextTickQueue.h"
+#include <JavaScriptCore/JSGlobalObject.h>
+#include <JavaScriptCore/Structure.h>
+#include <JavaScriptCore/JSInternalFieldObjectImplInlines.h>
+#include "ExtendedDOMClientIsoSubspaces.h"
+#include "ExtendedDOMIsoSubspaces.h"
+#include "BunClientData.h"
+
+namespace Bun {
+
+using namespace JSC;
+
+const JSC::ClassInfo JSNextTickQueue::s_info = { "JSNextTickQueue"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSNextTickQueue) };
+
+template<typename, JSC::SubspaceAccess mode>
+JSC::GCClient::IsoSubspace* JSNextTickQueue::subspaceFor(JSC::VM& vm)
+{
+ return WebCore::subspaceForImpl<JSNextTickQueue, WebCore::UseCustomHeapCellType::No>(
+ vm,
+ [](auto& spaces) { return spaces.m_clientSubspaceForJSNextTickQueue.get(); },
+ [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForJSNextTickQueue = std::forward<decltype(space)>(space); },
+ [](auto& spaces) { return spaces.m_subspaceForJSNextTickQueue.get(); },
+ [](auto& spaces, auto&& space) { spaces.m_subspaceForJSNextTickQueue = std::forward<decltype(space)>(space); });
+}
+
+JSNextTickQueue* JSNextTickQueue::create(VM& vm, Structure* structure)
+{
+ JSNextTickQueue* mod = new (NotNull, allocateCell<JSNextTickQueue>(vm)) JSNextTickQueue(vm, structure);
+ return mod;
+}
+Structure* JSNextTickQueue::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
+{
+ return Structure::create(vm, globalObject, prototype, TypeInfo(CellType, StructureFlags), info());
+}
+
+JSNextTickQueue::JSNextTickQueue(VM& vm, Structure* structure)
+ : Base(vm, structure)
+{
+}
+
+void JSNextTickQueue::finishCreation(VM& vm)
+{
+ Base::finishCreation(vm);
+}
+
+template<typename Visitor>
+void JSNextTickQueue::visitChildrenImpl(JSCell* cell, Visitor& visitor)
+{
+ auto* thisObject = jsCast<JSNextTickQueue*>(cell);
+ ASSERT_GC_OBJECT_INHERITS(thisObject, info());
+ Base::visitChildren(thisObject, visitor);
+}
+
+DEFINE_VISIT_CHILDREN(JSNextTickQueue);
+
+JSNextTickQueue* JSNextTickQueue::create(JSC::JSGlobalObject* globalObject)
+{
+ auto& vm = globalObject->vm();
+ auto* obj = create(vm, createStructure(vm, globalObject, jsNull()));
+ obj->finishCreation(vm);
+ return obj;
+}
+
+bool JSNextTickQueue::isEmpty()
+{
+ return !internalField(0) || internalField(0).get().asNumber() == 0;
+}
+
+void JSNextTickQueue::drain(JSC::VM& vm, JSC::JSGlobalObject* globalObject)
+{
+ bool mustResetContext = false;
+ if (isEmpty()) {
+ vm.drainMicrotasks();
+ mustResetContext = true;
+ }
+
+ if (!isEmpty()) {
+ if (mustResetContext) {
+ globalObject->m_asyncContextData.get()->putInternalField(vm, 0, jsUndefined());
+ }
+ auto* drainFn = internalField(2).get().getObject();
+
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ MarkedArgumentBuffer drainArgs;
+ JSC::call(globalObject, drainFn, drainArgs, "Failed to drain next tick queue"_s);
+ }
+}
+
+} \ No newline at end of file
diff --git a/src/bun.js/bindings/JSNextTickQueue.h b/src/bun.js/bindings/JSNextTickQueue.h
new file mode 100644
index 000000000..c3bd228cc
--- /dev/null
+++ b/src/bun.js/bindings/JSNextTickQueue.h
@@ -0,0 +1,41 @@
+#include "root.h"
+#include "headers-handwritten.h"
+
+#include "JavaScriptCore/JSCInlines.h"
+#include "BunClientData.h"
+#include <JavaScriptCore/JSInternalFieldObjectImpl.h>
+
+namespace Bun {
+using namespace JSC;
+
+class JSNextTickQueue : public JSC::JSInternalFieldObjectImpl<3> {
+public:
+ static constexpr unsigned numberOfInternalFields = 3;
+ using Base = JSC::JSInternalFieldObjectImpl<3>;
+
+ template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm);
+
+ JS_EXPORT_PRIVATE static JSNextTickQueue* create(VM&, Structure*);
+ static JSNextTickQueue* create(JSC::JSGlobalObject* globalObject);
+ static JSNextTickQueue* createWithInitialValues(VM&, Structure*);
+ static Structure* createStructure(VM&, JSGlobalObject*, JSValue);
+
+ static std::array<JSValue, numberOfInternalFields> initialValues()
+ {
+ return { {
+ jsNumber(-1),
+ jsUndefined(),
+ jsUndefined(),
+ } };
+ }
+
+ DECLARE_EXPORT_INFO;
+ DECLARE_VISIT_CHILDREN;
+
+ JSNextTickQueue(JSC::VM&, JSC::Structure*);
+ void finishCreation(JSC::VM&);
+
+ bool isEmpty();
+ void drain(JSC::VM& vm, JSC::JSGlobalObject* globalObject);
+};
+} \ No newline at end of file
diff --git a/src/bun.js/bindings/Process.cpp b/src/bun.js/bindings/Process.cpp
index 5c9c03dd2..252d00075 100644
--- a/src/bun.js/bindings/Process.cpp
+++ b/src/bun.js/bindings/Process.cpp
@@ -19,6 +19,7 @@
#include <termios.h>
#include <errno.h>
#include <sys/ioctl.h>
+#include "JSNextTickQueue.h"
#pragma mark - Node.js Process
@@ -160,63 +161,6 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionInternalGetWindowSize,
return JSC::JSValue::encode(jsBoolean(true));
}
-JSC_DEFINE_HOST_FUNCTION(Process_functionNextTick,
- (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
-{
- JSC::VM& vm = globalObject->vm();
- auto argCount = callFrame->argumentCount();
- if (argCount == 0) {
- auto scope = DECLARE_THROW_SCOPE(globalObject->vm());
- JSC::throwTypeError(globalObject, scope, "nextTick requires 1 argument (a function)"_s);
- return JSC::JSValue::encode(JSC::JSValue {});
- }
-
- JSC::JSValue job = callFrame->uncheckedArgument(0);
-
- if (!job.isObject() || !job.getObject()->isCallable()) {
- auto scope = DECLARE_THROW_SCOPE(globalObject->vm());
- JSC::throwTypeError(globalObject, scope, "nextTick expects a function"_s);
- return JSC::JSValue::encode(JSC::JSValue {});
- }
-
- Zig::GlobalObject* global = JSC::jsCast<Zig::GlobalObject*>(globalObject);
- JSC::JSValue asyncContextValue = globalObject->m_asyncContextData.get()->getInternalField(0);
-
- switch (callFrame->argumentCount()) {
- case 1: {
- global->queueMicrotask(global->performMicrotaskFunction(), job, asyncContextValue, JSC::JSValue {}, JSC::JSValue {});
- break;
- }
- case 2: {
- global->queueMicrotask(global->performMicrotaskFunction(), job, asyncContextValue, callFrame->uncheckedArgument(1), JSC::JSValue {});
- break;
- }
- case 3: {
- global->queueMicrotask(global->performMicrotaskFunction(), job, asyncContextValue, callFrame->uncheckedArgument(1), callFrame->uncheckedArgument(2));
- break;
- }
- default: {
- JSC::JSArray* args = JSC::constructEmptyArray(globalObject, nullptr, argCount - 1);
- if (UNLIKELY(!args)) {
- auto scope = DECLARE_THROW_SCOPE(vm);
- throwVMError(globalObject, scope, createOutOfMemoryError(globalObject));
- return JSC::JSValue::encode(JSC::JSValue {});
- }
-
- for (unsigned i = 1; i < argCount; i++) {
- args->putDirectIndex(globalObject, i - 1, callFrame->uncheckedArgument(i));
- }
-
- global->queueMicrotask(
- global->performMicrotaskVariadicFunction(), job, args, asyncContextValue, JSC::JSValue {});
-
- break;
- }
- }
-
- return JSC::JSValue::encode(jsUndefined());
-}
-
JSC_DECLARE_HOST_FUNCTION(Process_functionDlopen);
JSC_DEFINE_HOST_FUNCTION(Process_functionDlopen,
(JSC::JSGlobalObject * globalObject_, JSC::CallFrame* callFrame))
@@ -279,7 +223,7 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionDlopen,
}
}
- JSC::EncodedJSValue (*napi_register_module_v1)(JSC::JSGlobalObject* globalObject,
+ JSC::EncodedJSValue (*napi_register_module_v1)(JSC::JSGlobalObject * globalObject,
JSC::EncodedJSValue exports);
napi_register_module_v1 = reinterpret_cast<JSC::EncodedJSValue (*)(JSC::JSGlobalObject*,
@@ -1533,6 +1477,42 @@ static JSValue constructMemoryUsage(VM& vm, JSObject* processObject)
return memoryUsage;
}
+JSC_DEFINE_HOST_FUNCTION(jsFunctionReportUncaughtException, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
+{
+ JSValue arg0 = callFrame->argument(0);
+ Bun__reportUnhandledError(globalObject, JSValue::encode(arg0));
+ return JSValue::encode(jsUndefined());
+}
+
+JSC_DEFINE_HOST_FUNCTION(jsFunctionDrainMicrotaskQueue, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
+{
+ globalObject->vm().drainMicrotasks();
+ return JSValue::encode(jsUndefined());
+}
+
+static JSValue constructProcessNextTickFn(VM& vm, JSObject* processObject)
+{
+ JSGlobalObject* lexicalGlobalObject = processObject->globalObject();
+ Zig::GlobalObject* globalObject = jsCast<Zig::GlobalObject*>(lexicalGlobalObject);
+ JSValue nextTickQueueObject;
+ if (!globalObject->m_nextTickQueue) {
+ Bun::JSNextTickQueue* queue = Bun::JSNextTickQueue::create(globalObject);
+ globalObject->m_nextTickQueue.set(vm, globalObject, queue);
+ nextTickQueueObject = queue;
+ } else {
+ nextTickQueueObject = jsCast<Bun::JSNextTickQueue*>(globalObject->m_nextTickQueue.get());
+ }
+
+ JSC::JSFunction* initializer = JSC::JSFunction::create(vm, processObjectInternalsInitializeNextTickQueueCodeGenerator(vm), lexicalGlobalObject);
+ JSC::MarkedArgumentBuffer args;
+ args.append(processObject);
+ args.append(nextTickQueueObject);
+ args.append(JSC::JSFunction::create(vm, globalObject, 1, String(), jsFunctionDrainMicrotaskQueue, ImplementationVisibility::Private));
+ args.append(JSC::JSFunction::create(vm, globalObject, 1, String(), jsFunctionReportUncaughtException, ImplementationVisibility::Private));
+
+ return JSC::call(globalObject, initializer, JSC::getCallData(initializer), globalObject->globalThis(), args);
+}
+
static JSValue constructFeatures(VM& vm, JSObject* processObject)
{
// {
@@ -1742,7 +1722,7 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionKill,
mainModule JSBuiltin ReadOnly|Builtin|Accessor|Function 0
memoryUsage constructMemoryUsage PropertyCallback
moduleLoadList Process_stubEmptyArray PropertyCallback
- nextTick Process_functionNextTick Function 1
+ nextTick constructProcessNextTickFn PropertyCallback
openStdin Process_functionOpenStdin Function 0
pid constructPid PropertyCallback
platform constructPlatform PropertyCallback
diff --git a/src/bun.js/bindings/Process.lut.h b/src/bun.js/bindings/Process.lut.h
index 3f2d9255d..4086fb19e 100644
--- a/src/bun.js/bindings/Process.lut.h
+++ b/src/bun.js/bindings/Process.lut.h
@@ -179,7 +179,7 @@ static const struct HashTableValue processObjectTableValues[62] = {
{ "mainModule"_s, ((static_cast<unsigned>(PropertyAttribute::ReadOnly|PropertyAttribute::Builtin|PropertyAttribute::Accessor|PropertyAttribute::Function)) & ~PropertyAttribute::Function) | PropertyAttribute::Builtin, NoIntrinsic, { HashTableValue::BuiltinGeneratorType, processObjectMainModuleCodeGenerator, 0 } },
{ "memoryUsage"_s, static_cast<unsigned>(PropertyAttribute::PropertyCallback), NoIntrinsic, { HashTableValue::LazyPropertyType, constructMemoryUsage } },
{ "moduleLoadList"_s, static_cast<unsigned>(PropertyAttribute::PropertyCallback), NoIntrinsic, { HashTableValue::LazyPropertyType, Process_stubEmptyArray } },
- { "nextTick"_s, static_cast<unsigned>(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, Process_functionNextTick, 1 } },
+ { "nextTick"_s, static_cast<unsigned>(PropertyAttribute::PropertyCallback), NoIntrinsic, { HashTableValue::LazyPropertyType, constructProcessNextTickFn } },
{ "openStdin"_s, static_cast<unsigned>(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, Process_functionOpenStdin, 0 } },
{ "pid"_s, static_cast<unsigned>(PropertyAttribute::PropertyCallback), NoIntrinsic, { HashTableValue::LazyPropertyType, constructPid } },
{ "platform"_s, static_cast<unsigned>(PropertyAttribute::PropertyCallback), NoIntrinsic, { HashTableValue::LazyPropertyType, constructPlatform } },
diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp
index 7edbd42e6..286084b4d 100644
--- a/src/bun.js/bindings/ZigGlobalObject.cpp
+++ b/src/bun.js/bindings/ZigGlobalObject.cpp
@@ -130,6 +130,7 @@
#endif
#include "BunObject.h"
+#include "JSNextTickQueue.h"
using namespace Bun;
@@ -294,7 +295,7 @@ extern "C" void JSCInitialize(const char* envp[], size_t envc, void (*onCrash)(c
}
extern "C" void* Bun__getVM();
-extern "C" JSGlobalObject* Bun__getDefaultGlobal();
+extern "C" Zig::GlobalObject* Bun__getDefaultGlobal();
// Error.captureStackTrace may cause computeErrorInfo to be called twice
// Rather than figure out the plumbing in JSC, we just skip the next call
@@ -432,6 +433,44 @@ static String computeErrorInfo(JSC::VM& vm, Vector<StackFrame>& stackTrace, unsi
return computeErrorInfoWithoutPrepareStackTrace(vm, stackTrace, line, column, sourceURL, errorInstance);
}
+static void resetOnEachMicrotaskTick(JSC::VM& vm, Zig::GlobalObject* globalObject);
+
+static void checkIfNextTickWasCalledDuringMicrotask(JSC::VM& vm)
+{
+ auto* globalObject = Bun__getDefaultGlobal();
+ if (auto nextTickQueueValue = globalObject->m_nextTickQueue.get()) {
+ auto* queue = jsCast<Bun::JSNextTickQueue*>(nextTickQueueValue);
+ resetOnEachMicrotaskTick(vm, globalObject);
+ queue->drain(vm, globalObject);
+ }
+}
+
+static void cleanupAsyncHooksData(JSC::VM& vm)
+{
+ auto* globalObject = Bun__getDefaultGlobal();
+ globalObject->m_asyncContextData.get()->putInternalField(vm, 0, jsUndefined());
+ globalObject->asyncHooksNeedsCleanup = false;
+ if (!globalObject->m_nextTickQueue) {
+ vm.setOnEachMicrotaskTick(&checkIfNextTickWasCalledDuringMicrotask);
+ checkIfNextTickWasCalledDuringMicrotask(vm);
+ } else {
+ vm.setOnEachMicrotaskTick(nullptr);
+ }
+}
+
+static void resetOnEachMicrotaskTick(JSC::VM& vm, Zig::GlobalObject* globalObject)
+{
+ if (globalObject->asyncHooksNeedsCleanup) {
+ vm.setOnEachMicrotaskTick(&cleanupAsyncHooksData);
+ } else {
+ if (globalObject->m_nextTickQueue) {
+ vm.setOnEachMicrotaskTick(nullptr);
+ } else {
+ vm.setOnEachMicrotaskTick(&checkIfNextTickWasCalledDuringMicrotask);
+ }
+ }
+}
+
extern "C" JSC__JSGlobalObject* Zig__GlobalObject__create(void* console_client, int32_t executionContextId, bool miniMode, void* worker_ptr)
{
auto heapSize = miniMode ? JSC::HeapType::Small : JSC::HeapType::Large;
@@ -479,6 +518,16 @@ extern "C" JSC__JSGlobalObject* Zig__GlobalObject__create(void* console_client,
JSC::gcProtect(globalObject);
+ vm.setOnEachMicrotaskTick([](JSC::VM& vm) -> void {
+ auto* globalObject = Bun__getDefaultGlobal();
+ if (auto nextTickQueue = globalObject->m_nextTickQueue.get()) {
+ resetOnEachMicrotaskTick(vm, globalObject);
+ Bun::JSNextTickQueue* queue = jsCast<Bun::JSNextTickQueue*>(nextTickQueue);
+ queue->drain(vm, globalObject);
+ return;
+ }
+ });
+
vm.ref();
return globalObject;
}
@@ -1115,6 +1164,17 @@ JSC_DEFINE_HOST_FUNCTION(functionSetTimeout,
return JSC::JSValue::encode(JSC::JSValue {});
}
+#ifdef BUN_DEBUG
+ /** View the file name of the JS file that called this function
+ * from a debugger */
+ SourceOrigin sourceOrigin = callFrame->callerSourceOrigin(vm);
+ const char* fileName = sourceOrigin.string().utf8().data();
+ static const char* lastFileName = nullptr;
+ if (lastFileName != fileName) {
+ lastFileName = fileName;
+ }
+#endif
+
return Bun__Timer__setTimeout(globalObject, JSC::JSValue::encode(job), JSC::JSValue::encode(num), JSValue::encode(arguments));
}
@@ -1166,6 +1226,17 @@ JSC_DEFINE_HOST_FUNCTION(functionSetInterval,
return JSC::JSValue::encode(JSC::JSValue {});
}
+#ifdef BUN_DEBUG
+ /** View the file name of the JS file that called this function
+ * from a debugger */
+ SourceOrigin sourceOrigin = callFrame->callerSourceOrigin(vm);
+ const char* fileName = sourceOrigin.string().utf8().data();
+ static const char* lastFileName = nullptr;
+ if (lastFileName != fileName) {
+ lastFileName = fileName;
+ }
+#endif
+
return Bun__Timer__setInterval(globalObject, JSC::JSValue::encode(job), JSC::JSValue::encode(num), JSValue::encode(arguments));
}
@@ -1182,6 +1253,17 @@ JSC_DEFINE_HOST_FUNCTION(functionClearInterval,
JSC::JSValue num = callFrame->argument(0);
+#ifdef BUN_DEBUG
+ /** View the file name of the JS file that called this function
+ * from a debugger */
+ SourceOrigin sourceOrigin = callFrame->callerSourceOrigin(vm);
+ const char* fileName = sourceOrigin.string().utf8().data();
+ static const char* lastFileName = nullptr;
+ if (lastFileName != fileName) {
+ lastFileName = fileName;
+ }
+#endif
+
return Bun__Timer__clearInterval(globalObject, JSC::JSValue::encode(num));
}
@@ -1198,6 +1280,17 @@ JSC_DEFINE_HOST_FUNCTION(functionClearTimeout,
JSC::JSValue num = callFrame->argument(0);
+#ifdef BUN_DEBUG
+ /** View the file name of the JS file that called this function
+ * from a debugger */
+ SourceOrigin sourceOrigin = callFrame->callerSourceOrigin(vm);
+ const char* fileName = sourceOrigin.string().utf8().data();
+ static const char* lastFileName = nullptr;
+ if (lastFileName != fileName) {
+ lastFileName = fileName;
+ }
+#endif
+
return Bun__Timer__clearTimeout(globalObject, JSC::JSValue::encode(num));
}
@@ -1393,12 +1486,6 @@ JSC_DEFINE_HOST_FUNCTION(functionCallback, (JSC::JSGlobalObject * globalObject,
return JSC::JSValue::encode(JSC::call(globalObject, callback, callData, JSC::jsUndefined(), JSC::MarkedArgumentBuffer()));
}
-static void cleanupAsyncHooksData(JSC::VM& vm)
-{
- vm.setOnEachMicrotaskTick(nullptr);
- Bun__getDefaultGlobal()->m_asyncContextData.get()->putInternalField(vm, 0, jsUndefined());
-}
-
// $lazy("async_hooks").cleanupLater
JSC_DEFINE_HOST_FUNCTION(asyncHooksCleanupLater, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
{
@@ -1406,7 +1493,9 @@ JSC_DEFINE_HOST_FUNCTION(asyncHooksCleanupLater, (JSC::JSGlobalObject * globalOb
// - nobody else uses setOnEachMicrotaskTick
// - this is called by js if we set async context in a way we may not clear it
// - AsyncLocalStorage.prototype.run cleans up after itself and does not call this cb
- globalObject->vm().setOnEachMicrotaskTick(&cleanupAsyncHooksData);
+ auto* global = jsCast<Zig::GlobalObject*>(globalObject);
+ global->asyncHooksNeedsCleanup = true;
+ resetOnEachMicrotaskTick(globalObject->vm(), global);
return JSC::JSValue::encode(JSC::jsUndefined());
}
@@ -3955,6 +4044,23 @@ extern "C" bool JSC__JSGlobalObject__startRemoteInspector(JSC__JSGlobalObject* g
#endif
}
+void GlobalObject::drainMicrotasks()
+{
+ auto& vm = this->vm();
+ if (auto nextTickQueue = this->m_nextTickQueue.get()) {
+ Bun::JSNextTickQueue* queue = jsCast<Bun::JSNextTickQueue*>(nextTickQueue);
+ queue->drain(vm, this);
+ return;
+ }
+
+ vm.drainMicrotasks();
+}
+
+extern "C" void JSC__JSGlobalObject__drainMicrotasks(Zig::GlobalObject* globalObject)
+{
+ globalObject->drainMicrotasks();
+}
+
template<typename Visitor>
void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor)
{
@@ -4008,6 +4114,8 @@ void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor)
visitor.append(thisObject->m_JSWebSocketSetterValue);
visitor.append(thisObject->m_JSWorkerSetterValue);
+ visitor.append(thisObject->m_nextTickQueue);
+
thisObject->m_JSArrayBufferSinkClassStructure.visit(visitor);
thisObject->m_JSBufferListClassStructure.visit(visitor);
thisObject->m_JSFFIFunctionStructure.visit(visitor);
diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h
index 029f90132..e622016de 100644
--- a/src/bun.js/bindings/ZigGlobalObject.h
+++ b/src/bun.js/bindings/ZigGlobalObject.h
@@ -296,6 +296,8 @@ public:
return m_processEnvObject.getInitializedOnMainThread(this);
}
+ void drainMicrotasks();
+
void handleRejectedPromises();
void initGeneratedLazyClasses();
@@ -363,6 +365,8 @@ public:
return func;
}
+ bool asyncHooksNeedsCleanup = false;
+
/**
* WARNING: You must update visitChildrenImpl() if you add a new field.
*
@@ -381,6 +385,7 @@ public:
mutable WriteBarrier<JSFunction> m_readableStreamToText;
mutable WriteBarrier<JSFunction> m_readableStreamToFormData;
+ mutable WriteBarrier<Unknown> m_nextTickQueue;
mutable WriteBarrier<Unknown> m_BunCommonJSModuleValue;
mutable WriteBarrier<Unknown> m_JSBroadcastChannelSetterValue;
mutable WriteBarrier<Unknown> m_JSBufferSetterValue;
diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp
index ca072f2b1..6413e0470 100644
--- a/src/bun.js/bindings/bindings.cpp
+++ b/src/bun.js/bindings/bindings.cpp
@@ -2527,7 +2527,6 @@ JSC__JSInternalPromise*
JSC__JSModuleLoader__loadAndEvaluateModule(JSC__JSGlobalObject* globalObject,
const BunString* arg1)
{
- globalObject->vm().drainMicrotasks();
auto name = Bun::toWTFString(*arg1);
name.impl()->ref();
@@ -2546,9 +2545,7 @@ JSC__JSModuleLoader__loadAndEvaluateModule(JSC__JSGlobalObject* globalObject,
JSC::JSInternalPromise::rejectedPromise(globalObject, callFrame->argument(0)));
});
- globalObject->vm().drainMicrotasks();
auto result = promise->then(globalObject, resolverFunction, rejecterFunction);
- globalObject->vm().drainMicrotasks();
// if (promise->status(globalObject->vm()) ==
// JSC::JSPromise::Status::Fulfilled) {
diff --git a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h
index 4c09df6a5..e21a62bf8 100644
--- a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h
+++ b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h
@@ -42,6 +42,7 @@ public:
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForProcessObject;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForInternalModuleRegistry;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForBunInspectorConnection;
+ std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForJSNextTickQueue;
#include "ZigGeneratedClasses+DOMClientIsoSubspaces.h"
/* --- bun --- */
diff --git a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h
index 2b834cf3c..806aa4454 100644
--- a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h
+++ b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h
@@ -42,7 +42,7 @@ public:
std::unique_ptr<IsoSubspace> m_subspaceForProcessObject;
std::unique_ptr<IsoSubspace> m_subspaceForInternalModuleRegistry;
std::unique_ptr<IsoSubspace> m_subspaceForBunInspectorConnection;
-
+ std::unique_ptr<IsoSubspace> m_subspaceForJSNextTickQueue;
#include "ZigGeneratedClasses+DOMIsoSubspaces.h"
/*-- BUN --*/
diff --git a/src/bun.js/event_loop.zig b/src/bun.js/event_loop.zig
index 640a9276c..f1367c239 100644
--- a/src/bun.js/event_loop.zig
+++ b/src/bun.js/event_loop.zig
@@ -529,14 +529,14 @@ pub const EventLoop = struct {
this.virtual_machine.event_loop_handle.?.tick();
}
}
-
- pub fn drainMicrotasksWithVM(this: *EventLoop, vm: *JSC.VM) void {
- vm.drainMicrotasks();
+ extern fn JSC__JSGlobalObject__drainMicrotasks(*JSC.JSGlobalObject) void;
+ fn drainMicrotasksWithGlobal(this: *EventLoop, globalObject: *JSC.JSGlobalObject) void {
+ JSC__JSGlobalObject__drainMicrotasks(globalObject);
this.drainDeferredTasks();
}
pub fn drainMicrotasks(this: *EventLoop) void {
- this.drainMicrotasksWithVM(this.global.vm());
+ this.drainMicrotasksWithGlobal(this.global);
}
pub fn ensureAliveForOneTick(this: *EventLoop) void {
@@ -666,7 +666,7 @@ pub const EventLoop = struct {
}
global_vm.releaseWeakRefs();
- this.drainMicrotasksWithVM(global_vm);
+ this.drainMicrotasksWithGlobal(global);
}
this.tasks.head = if (this.tasks.count == 0) 0 else this.tasks.head;
@@ -824,13 +824,14 @@ pub const EventLoop = struct {
this.processGCTimer();
- var global_vm = ctx.global.vm();
+ var global = ctx.global;
+ var global_vm = global.vm();
while (true) {
while (this.tickWithCount() > 0) : (this.global.handleRejectedPromises()) {
this.tickConcurrent();
} else {
global_vm.releaseWeakRefs();
- this.drainMicrotasksWithVM(global_vm);
+ this.drainMicrotasksWithGlobal(global);
this.tickConcurrent();
if (this.tasks.count > 0) continue;
}
diff --git a/src/bun_js.zig b/src/bun_js.zig
index 46942b849..0605c57a1 100644
--- a/src/bun_js.zig
+++ b/src/bun_js.zig
@@ -241,6 +241,8 @@ pub const Run = struct {
pub fn start(this: *Run) void {
var vm = this.vm;
vm.hot_reload = this.ctx.debug.hot_reload;
+ vm.onUnhandledRejection = &onUnhandledRejectionBeforeClose;
+
if (this.ctx.debug.hot_reload != .none) {
JSC.HotReloader.enableHotModuleReloading(vm);
}
diff --git a/src/js/builtins/ProcessObjectInternals.ts b/src/js/builtins/ProcessObjectInternals.ts
index e6c04c90f..2bb8648df 100644
--- a/src/js/builtins/ProcessObjectInternals.ts
+++ b/src/js/builtins/ProcessObjectInternals.ts
@@ -1,4 +1,5 @@
/*
+ * Copyright Joyent, Inc. and other Node contributors.
* Copyright 2023 Codeblog Corp. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -199,3 +200,204 @@ export function getStdinStream(fd) {
return stream;
}
+
+export function initializeNextTickQueue(process, nextTickQueue, drainMicrotasksFn, reportUncaughtExceptionFn) {
+ var queue;
+ var process;
+ var nextTickQueue = nextTickQueue;
+ var drainMicrotasks = drainMicrotasksFn;
+ var reportUncaughtException = reportUncaughtExceptionFn;
+
+ function validateFunction(cb) {
+ if (typeof cb !== "function") {
+ const err = new TypeError(`The "callback" argument must be of type "function". Received type ${typeof cb}`);
+ err.code = "ERR_INVALID_ARG_TYPE";
+ throw err;
+ }
+ }
+
+ var setup;
+ setup = () => {
+ queue = (function createQueue() {
+ // Currently optimal queue size, tested on V8 6.0 - 6.6. Must be power of two.
+ const kSize = 2048;
+ const kMask = kSize - 1;
+
+ // The FixedQueue is implemented as a singly-linked list of fixed-size
+ // circular buffers. It looks something like this:
+ //
+ // head tail
+ // | |
+ // v v
+ // +-----------+ <-----\ +-----------+ <------\ +-----------+
+ // | [null] | \----- | next | \------- | next |
+ // +-----------+ +-----------+ +-----------+
+ // | item | <-- bottom | item | <-- bottom | [empty] |
+ // | item | | item | | [empty] |
+ // | item | | item | | [empty] |
+ // | item | | item | | [empty] |
+ // | item | | item | bottom --> | item |
+ // | item | | item | | item |
+ // | ... | | ... | | ... |
+ // | item | | item | | item |
+ // | item | | item | | item |
+ // | [empty] | <-- top | item | | item |
+ // | [empty] | | item | | item |
+ // | [empty] | | [empty] | <-- top top --> | [empty] |
+ // +-----------+ +-----------+ +-----------+
+ //
+ // Or, if there is only one circular buffer, it looks something
+ // like either of these:
+ //
+ // head tail head tail
+ // | | | |
+ // v v v v
+ // +-----------+ +-----------+
+ // | [null] | | [null] |
+ // +-----------+ +-----------+
+ // | [empty] | | item |
+ // | [empty] | | item |
+ // | item | <-- bottom top --> | [empty] |
+ // | item | | [empty] |
+ // | [empty] | <-- top bottom --> | item |
+ // | [empty] | | item |
+ // +-----------+ +-----------+
+ //
+ // Adding a value means moving `top` forward by one, removing means
+ // moving `bottom` forward by one. After reaching the end, the queue
+ // wraps around.
+ //
+ // When `top === bottom` the current queue is empty and when
+ // `top + 1 === bottom` it's full. This wastes a single space of storage
+ // but allows much quicker checks.
+
+ class FixedCircularBuffer {
+ constructor() {
+ this.bottom = 0;
+ this.top = 0;
+ this.list = $newArrayWithSize(kSize);
+ this.next = null;
+ }
+
+ isEmpty() {
+ return this.top === this.bottom;
+ }
+
+ isFull() {
+ return ((this.top + 1) & kMask) === this.bottom;
+ }
+
+ push(data) {
+ this.list[this.top] = data;
+ this.top = (this.top + 1) & kMask;
+ }
+
+ shift() {
+ var { list, bottom } = this;
+ const nextItem = list[bottom];
+ if (nextItem === undefined) return null;
+ list[bottom] = undefined;
+ this.bottom = (bottom + 1) & kMask;
+ return nextItem;
+ }
+ }
+
+ class FixedQueue {
+ constructor() {
+ this.head = this.tail = new FixedCircularBuffer();
+ }
+
+ isEmpty() {
+ return this.head.isEmpty();
+ }
+
+ push(data) {
+ if (this.head.isFull()) {
+ // Head is full: Creates a new queue, sets the old queue's `.next` to it,
+ // and sets it as the new main queue.
+ this.head = this.head.next = new FixedCircularBuffer();
+ }
+ this.head.push(data);
+ }
+
+ shift() {
+ const tail = this.tail;
+ const next = tail.shift();
+ if (tail.isEmpty() && tail.next !== null) {
+ // If there is another queue, it forms the new tail.
+ this.tail = tail.next;
+ tail.next = null;
+ }
+ return next;
+ }
+ }
+
+ return new FixedQueue();
+ })();
+
+ function processTicksAndRejections() {
+ var tock;
+ do {
+ while ((tock = queue.shift()) !== null) {
+ var callback = tock.callback;
+ var args = tock.args;
+ var frame = tock.frame;
+ var restore = $getInternalField($asyncContext, 0);
+ $putInternalField($asyncContext, 0, frame);
+ try {
+ if (args === undefined) {
+ callback();
+ } else {
+ switch (args.length) {
+ case 1:
+ callback(args[0]);
+ break;
+ case 2:
+ callback(args[0], args[1]);
+ break;
+ case 3:
+ callback(args[0], args[1], args[2]);
+ break;
+ case 4:
+ callback(args[0], args[1], args[2], args[3]);
+ break;
+ default:
+ callback(...args);
+ break;
+ }
+ }
+ } catch (e) {
+ reportUncaughtException(e);
+ } finally {
+ $putInternalField($asyncContext, 0, restore);
+ }
+ }
+
+ drainMicrotasks();
+ } while (!queue.isEmpty());
+ }
+
+ $putInternalField(nextTickQueue, 0, 0);
+ $putInternalField(nextTickQueue, 1, queue);
+ $putInternalField(nextTickQueue, 2, processTicksAndRejections);
+ setup = undefined;
+ };
+
+ function nextTick(cb, args) {
+ validateFunction(cb);
+ if (setup) {
+ setup();
+ process = globalThis.process;
+ }
+ if (process._exiting) return;
+
+ queue.push({
+ callback: cb,
+ args: $argumentCount() > 1 ? Array.prototype.slice.$call(arguments, 1) : undefined,
+ frame: $getInternalField($asyncContext, 0),
+ });
+ $putInternalField(nextTickQueue, 0, 1);
+ }
+
+ return nextTick;
+}
diff --git a/src/js/out/WebCoreJSBuiltins.cpp b/src/js/out/WebCoreJSBuiltins.cpp
index 1003fd522..d767a3c39 100644
--- a/src/js/out/WebCoreJSBuiltins.cpp
+++ b/src/js/out/WebCoreJSBuiltins.cpp
@@ -658,6 +658,14 @@ const int s_processObjectInternalsGetStdinStreamCodeLength = 1386;
static const JSC::Intrinsic s_processObjectInternalsGetStdinStreamCodeIntrinsic = JSC::NoIntrinsic;
const char* const s_processObjectInternalsGetStdinStreamCode = "(function (fd){\"use strict\";var reader,readerRef;function ref(){reader\?\?=@Bun.stdin.stream().getReader(),readerRef\?\?=setInterval(()=>{},1<<30)}function unref(){if(readerRef)clearInterval(readerRef),readerRef=@undefined;if(reader)reader.cancel(),reader=@undefined}const stream=new((@getInternalField(@internalModuleRegistry,44))||(@createInternalModuleById(44))).ReadStream(fd),originalOn=stream.on;stream.on=function(event,listener){if(event===\"readable\")ref();return originalOn.call(this,event,listener)},stream.fd=fd;const originalPause=stream.pause;stream.pause=function(){return unref(),originalPause.call(this)};const originalResume=stream.resume;stream.resume=function(){return ref(),originalResume.call(this)};async function internalRead(stream2){try{var done,value;const read=reader\?.readMany();if(@isPromise(read))({done,value}=await read);else({done,value}=read);if(!done){stream2.push(value[0]);const length=value.length;for(let i=1;i<length;i++)stream2.push(value[i])}else stream2.emit(\"end\"),stream2.pause()}catch(err){stream2.destroy(err)}}return stream._read=function(size){internalRead(this)},stream.on(\"resume\",()=>{ref(),stream._undestroy()}),stream._readableState.reading=!1,stream.on(\"pause\",()=>{process.nextTick(()=>{if(!stream.readableFlowing)stream._readableState.reading=!1})}),stream.on(\"close\",()=>{process.nextTick(()=>{stream.destroy(),unref()})}),stream})\n";
+// initializeNextTickQueue
+const JSC::ConstructAbility s_processObjectInternalsInitializeNextTickQueueCodeConstructAbility = JSC::ConstructAbility::CannotConstruct;
+const JSC::ConstructorKind s_processObjectInternalsInitializeNextTickQueueCodeConstructorKind = JSC::ConstructorKind::None;
+const JSC::ImplementationVisibility s_processObjectInternalsInitializeNextTickQueueCodeImplementationVisibility = JSC::ImplementationVisibility::Public;
+const int s_processObjectInternalsInitializeNextTickQueueCodeLength = 2336;
+static const JSC::Intrinsic s_processObjectInternalsInitializeNextTickQueueCodeIntrinsic = JSC::NoIntrinsic;
+const char* const s_processObjectInternalsInitializeNextTickQueueCode = "(function (process,nextTickQueue,drainMicrotasksFn,reportUncaughtExceptionFn){\"use strict\";var queue,process,nextTickQueue=nextTickQueue,drainMicrotasks=drainMicrotasksFn,reportUncaughtException=reportUncaughtExceptionFn;function validateFunction(cb){if(typeof cb!==\"function\"){const err=@makeTypeError(`The \"callback\" argument must be of type \"function\". Received type ${typeof cb}`);throw err.code=\"ERR_INVALID_ARG_TYPE\",err}}var setup=()=>{queue=function createQueue(){class FixedCircularBuffer{constructor(){this.bottom=0,this.top=0,this.list=@newArrayWithSize(2048),this.next=null}isEmpty(){return this.top===this.bottom}isFull(){return(this.top+1&2047)===this.bottom}push(data){this.list[this.top]=data,this.top=this.top+1&2047}shift(){var{list,bottom}=this;const nextItem=list[bottom];if(nextItem===@undefined)return null;return list[bottom]=@undefined,this.bottom=bottom+1&2047,nextItem}}class FixedQueue{constructor(){this.head=this.tail=new FixedCircularBuffer}isEmpty(){return this.head.isEmpty()}push(data){if(this.head.isFull())this.head=this.head.next=new FixedCircularBuffer;this.head.push(data)}shift(){const tail=this.tail,next=tail.shift();if(tail.isEmpty()&&tail.next!==null)this.tail=tail.next,tail.next=null;return next}}return new FixedQueue}();function processTicksAndRejections(){var tock;do{while((tock=queue.shift())!==null){var{callback,args,frame}=tock,restore=@getInternalField(@asyncContext,0);@putInternalField(@asyncContext,0,frame);try{if(args===@undefined)callback();else switch(args.length){case 1:callback(args[0]);break;case 2:callback(args[0],args[1]);break;case 3:callback(args[0],args[1],args[2]);break;case 4:callback(args[0],args[1],args[2],args[3]);break;default:callback(...args);break}}catch(e){reportUncaughtException(e)}finally{@putInternalField(@asyncContext,0,restore)}}drainMicrotasks()}while(!queue.isEmpty())}@putInternalField(nextTickQueue,0,0),@putInternalField(nextTickQueue,1,queue),@putInternalField(nextTickQueue,2,processTicksAndRejections),setup=@undefined};function nextTick(cb,args){if(validateFunction(cb),setup)setup(),process=globalThis.process;if(process._exiting)return;queue.push({callback:cb,args:@argumentCount()>1\?@Array.prototype.slice.@call(arguments,1):@undefined,frame:@getInternalField(@asyncContext,0)}),@putInternalField(nextTickQueue,0,1)}return nextTick})\n";
+
#define DEFINE_BUILTIN_GENERATOR(codeName, functionName, overriddenName, argumentCount) \
JSC::FunctionExecutable* codeName##Generator(JSC::VM& vm) \
{\
diff --git a/src/js/out/WebCoreJSBuiltins.h b/src/js/out/WebCoreJSBuiltins.h
index cf28fa82a..4fc91dbd9 100644
--- a/src/js/out/WebCoreJSBuiltins.h
+++ b/src/js/out/WebCoreJSBuiltins.h
@@ -1179,20 +1179,31 @@ extern const JSC::ConstructAbility s_processObjectInternalsGetStdinStreamCodeCon
extern const JSC::ConstructorKind s_processObjectInternalsGetStdinStreamCodeConstructorKind;
extern const JSC::ImplementationVisibility s_processObjectInternalsGetStdinStreamCodeImplementationVisibility;
+// initializeNextTickQueue
+#define WEBCORE_BUILTIN_PROCESSOBJECTINTERNALS_INITIALIZENEXTTICKQUEUE 1
+extern const char* const s_processObjectInternalsInitializeNextTickQueueCode;
+extern const int s_processObjectInternalsInitializeNextTickQueueCodeLength;
+extern const JSC::ConstructAbility s_processObjectInternalsInitializeNextTickQueueCodeConstructAbility;
+extern const JSC::ConstructorKind s_processObjectInternalsInitializeNextTickQueueCodeConstructorKind;
+extern const JSC::ImplementationVisibility s_processObjectInternalsInitializeNextTickQueueCodeImplementationVisibility;
+
#define WEBCORE_FOREACH_PROCESSOBJECTINTERNALS_BUILTIN_DATA(macro) \
macro(binding, processObjectInternalsBinding, 1) \
macro(getStdioWriteStream, processObjectInternalsGetStdioWriteStream, 1) \
macro(getStdinStream, processObjectInternalsGetStdinStream, 1) \
+ macro(initializeNextTickQueue, processObjectInternalsInitializeNextTickQueue, 4) \
#define WEBCORE_FOREACH_PROCESSOBJECTINTERNALS_BUILTIN_CODE(macro) \
macro(processObjectInternalsBindingCode, binding, ASCIILiteral(), s_processObjectInternalsBindingCodeLength) \
macro(processObjectInternalsGetStdioWriteStreamCode, getStdioWriteStream, ASCIILiteral(), s_processObjectInternalsGetStdioWriteStreamCodeLength) \
macro(processObjectInternalsGetStdinStreamCode, getStdinStream, ASCIILiteral(), s_processObjectInternalsGetStdinStreamCodeLength) \
+ macro(processObjectInternalsInitializeNextTickQueueCode, initializeNextTickQueue, ASCIILiteral(), s_processObjectInternalsInitializeNextTickQueueCodeLength) \
#define WEBCORE_FOREACH_PROCESSOBJECTINTERNALS_BUILTIN_FUNCTION_NAME(macro) \
macro(binding) \
macro(getStdioWriteStream) \
macro(getStdinStream) \
+ macro(initializeNextTickQueue) \
#define DECLARE_BUILTIN_GENERATOR(codeName, functionName, overriddenName, argumentCount) \
JSC::FunctionExecutable* codeName##Generator(JSC::VM&);
diff --git a/test/js/bun/spawn/exit-code.test.ts b/test/js/bun/spawn/exit-code.test.ts
index cda76a395..1b97179f6 100644
--- a/test/js/bun/spawn/exit-code.test.ts
+++ b/test/js/bun/spawn/exit-code.test.ts
@@ -17,6 +17,11 @@ it("unhandled promise rejection reports exit code 1", () => {
expect(exitCode).toBe(1);
});
+it("handled promise rejection reports exit code 0", () => {
+ const { exitCode } = spawnSync([bunExe(), import.meta.dir + "/exit-code-handled-throw.js"]);
+ expect(exitCode).toBe(1);
+});
+
it("process.exit(0) works", () => {
const { exitCode } = spawnSync([bunExe(), import.meta.dir + "/exit-code-0.js"]);
expect(exitCode).toBe(0);
diff --git a/test/js/node/process/process-nexttick.test.js b/test/js/node/process/process-nexttick.test.js
index becf3c236..6f1eee6ba 100644
--- a/test/js/node/process/process-nexttick.test.js
+++ b/test/js/node/process/process-nexttick.test.js
@@ -1,4 +1,8 @@
-import { it } from "bun:test";
+// Running this file in jest/vitest does not work as expected. Jest & Vitest
+// mess with timers, producing unreliable results. You must manually test this
+// in Node.
+import { test, expect, it } from "bun:test";
+const isBun = !!process.versions.bun;
it("process.nextTick", async () => {
// You can verify this test is correct by copy pasting this into a browser's console and checking it doesn't throw an error.
@@ -52,25 +56,35 @@ it("process.nextTick", async () => {
});
{
- var passed = false;
+ let passed = false;
try {
queueMicrotask(1234);
} catch (exception) {
- passed = exception instanceof TypeError;
+ if (isBun) {
+ passed = exception instanceof TypeError;
+ } else {
+ // Node.js throws a non-TypeError TypeError
+ passed = exception instanceof Error && exception.name === "TypeError";
+ }
}
- if (!passed) throw new Error("queueMicrotask should throw a TypeError if the argument is not a function");
+ if (!passed) throw new Error("1: queueMicrotask should throw a TypeError if the argument is not a function");
}
{
- var passed = false;
+ let passed = false;
try {
queueMicrotask();
} catch (exception) {
- passed = exception instanceof TypeError;
+ if (isBun) {
+ passed = exception instanceof TypeError;
+ } else {
+ // Node.js throws a non-TypeError TypeError
+ passed = exception instanceof Error && exception.name === "TypeError";
+ }
}
- if (!passed) throw new Error("queueMicrotask should throw a TypeError if the argument is empty");
+ if (!passed) throw new Error("2: queueMicrotask should throw a TypeError if the argument is empty");
}
});
@@ -97,3 +111,925 @@ it("process.nextTick 5 args", async () => {
}, ...args);
});
});
+
+it("process.nextTick runs after queueMicrotask", async () => {
+ var resolve;
+ var promise = new Promise(_resolve => {
+ resolve = _resolve;
+ });
+
+ const order = [];
+ var nextTickI = 0;
+ var microtaskI = 0;
+ var remaining = 400;
+ var runs = [];
+ for (let i = 0; i < 100; i++) {
+ queueMicrotask(() => {
+ runs.push(queueMicrotask.name);
+ order.push("queueMicrotask " + microtaskI++);
+ if (--remaining === 0) resolve(order);
+ });
+ process.nextTick(() => {
+ runs.push(process.nextTick.name);
+ order.push("process.nextTick " + nextTickI++);
+ if (--remaining === 0) resolve(order);
+ });
+ }
+
+ for (let i = 0; i < 100; i++) {
+ queueMicrotask(() => {
+ runs.push(queueMicrotask.name);
+ order.push("queueMicrotask " + microtaskI++);
+ if (--remaining === 0) resolve(order);
+ });
+ }
+
+ for (let i = 0; i < 100; i++) {
+ process.nextTick(() => {
+ runs.push(process.nextTick.name);
+ order.push("process.nextTick " + nextTickI++);
+ if (--remaining === 0) resolve(order);
+ });
+ }
+
+ await promise;
+ expect({
+ order,
+ runs,
+ }).toEqual({
+ "order": [
+ "process.nextTick 0",
+ "process.nextTick 1",
+ "process.nextTick 2",
+ "process.nextTick 3",
+ "process.nextTick 4",
+ "process.nextTick 5",
+ "process.nextTick 6",
+ "process.nextTick 7",
+ "process.nextTick 8",
+ "process.nextTick 9",
+ "process.nextTick 10",
+ "process.nextTick 11",
+ "process.nextTick 12",
+ "process.nextTick 13",
+ "process.nextTick 14",
+ "process.nextTick 15",
+ "process.nextTick 16",
+ "process.nextTick 17",
+ "process.nextTick 18",
+ "process.nextTick 19",
+ "process.nextTick 20",
+ "process.nextTick 21",
+ "process.nextTick 22",
+ "process.nextTick 23",
+ "process.nextTick 24",
+ "process.nextTick 25",
+ "process.nextTick 26",
+ "process.nextTick 27",
+ "process.nextTick 28",
+ "process.nextTick 29",
+ "process.nextTick 30",
+ "process.nextTick 31",
+ "process.nextTick 32",
+ "process.nextTick 33",
+ "process.nextTick 34",
+ "process.nextTick 35",
+ "process.nextTick 36",
+ "process.nextTick 37",
+ "process.nextTick 38",
+ "process.nextTick 39",
+ "process.nextTick 40",
+ "process.nextTick 41",
+ "process.nextTick 42",
+ "process.nextTick 43",
+ "process.nextTick 44",
+ "process.nextTick 45",
+ "process.nextTick 46",
+ "process.nextTick 47",
+ "process.nextTick 48",
+ "process.nextTick 49",
+ "process.nextTick 50",
+ "process.nextTick 51",
+ "process.nextTick 52",
+ "process.nextTick 53",
+ "process.nextTick 54",
+ "process.nextTick 55",
+ "process.nextTick 56",
+ "process.nextTick 57",
+ "process.nextTick 58",
+ "process.nextTick 59",
+ "process.nextTick 60",
+ "process.nextTick 61",
+ "process.nextTick 62",
+ "process.nextTick 63",
+ "process.nextTick 64",
+ "process.nextTick 65",
+ "process.nextTick 66",
+ "process.nextTick 67",
+ "process.nextTick 68",
+ "process.nextTick 69",
+ "process.nextTick 70",
+ "process.nextTick 71",
+ "process.nextTick 72",
+ "process.nextTick 73",
+ "process.nextTick 74",
+ "process.nextTick 75",
+ "process.nextTick 76",
+ "process.nextTick 77",
+ "process.nextTick 78",
+ "process.nextTick 79",
+ "process.nextTick 80",
+ "process.nextTick 81",
+ "process.nextTick 82",
+ "process.nextTick 83",
+ "process.nextTick 84",
+ "process.nextTick 85",
+ "process.nextTick 86",
+ "process.nextTick 87",
+ "process.nextTick 88",
+ "process.nextTick 89",
+ "process.nextTick 90",
+ "process.nextTick 91",
+ "process.nextTick 92",
+ "process.nextTick 93",
+ "process.nextTick 94",
+ "process.nextTick 95",
+ "process.nextTick 96",
+ "process.nextTick 97",
+ "process.nextTick 98",
+ "process.nextTick 99",
+ "process.nextTick 100",
+ "process.nextTick 101",
+ "process.nextTick 102",
+ "process.nextTick 103",
+ "process.nextTick 104",
+ "process.nextTick 105",
+ "process.nextTick 106",
+ "process.nextTick 107",
+ "process.nextTick 108",
+ "process.nextTick 109",
+ "process.nextTick 110",
+ "process.nextTick 111",
+ "process.nextTick 112",
+ "process.nextTick 113",
+ "process.nextTick 114",
+ "process.nextTick 115",
+ "process.nextTick 116",
+ "process.nextTick 117",
+ "process.nextTick 118",
+ "process.nextTick 119",
+ "process.nextTick 120",
+ "process.nextTick 121",
+ "process.nextTick 122",
+ "process.nextTick 123",
+ "process.nextTick 124",
+ "process.nextTick 125",
+ "process.nextTick 126",
+ "process.nextTick 127",
+ "process.nextTick 128",
+ "process.nextTick 129",
+ "process.nextTick 130",
+ "process.nextTick 131",
+ "process.nextTick 132",
+ "process.nextTick 133",
+ "process.nextTick 134",
+ "process.nextTick 135",
+ "process.nextTick 136",
+ "process.nextTick 137",
+ "process.nextTick 138",
+ "process.nextTick 139",
+ "process.nextTick 140",
+ "process.nextTick 141",
+ "process.nextTick 142",
+ "process.nextTick 143",
+ "process.nextTick 144",
+ "process.nextTick 145",
+ "process.nextTick 146",
+ "process.nextTick 147",
+ "process.nextTick 148",
+ "process.nextTick 149",
+ "process.nextTick 150",
+ "process.nextTick 151",
+ "process.nextTick 152",
+ "process.nextTick 153",
+ "process.nextTick 154",
+ "process.nextTick 155",
+ "process.nextTick 156",
+ "process.nextTick 157",
+ "process.nextTick 158",
+ "process.nextTick 159",
+ "process.nextTick 160",
+ "process.nextTick 161",
+ "process.nextTick 162",
+ "process.nextTick 163",
+ "process.nextTick 164",
+ "process.nextTick 165",
+ "process.nextTick 166",
+ "process.nextTick 167",
+ "process.nextTick 168",
+ "process.nextTick 169",
+ "process.nextTick 170",
+ "process.nextTick 171",
+ "process.nextTick 172",
+ "process.nextTick 173",
+ "process.nextTick 174",
+ "process.nextTick 175",
+ "process.nextTick 176",
+ "process.nextTick 177",
+ "process.nextTick 178",
+ "process.nextTick 179",
+ "process.nextTick 180",
+ "process.nextTick 181",
+ "process.nextTick 182",
+ "process.nextTick 183",
+ "process.nextTick 184",
+ "process.nextTick 185",
+ "process.nextTick 186",
+ "process.nextTick 187",
+ "process.nextTick 188",
+ "process.nextTick 189",
+ "process.nextTick 190",
+ "process.nextTick 191",
+ "process.nextTick 192",
+ "process.nextTick 193",
+ "process.nextTick 194",
+ "process.nextTick 195",
+ "process.nextTick 196",
+ "process.nextTick 197",
+ "process.nextTick 198",
+ "process.nextTick 199",
+ "queueMicrotask 0",
+ "queueMicrotask 1",
+ "queueMicrotask 2",
+ "queueMicrotask 3",
+ "queueMicrotask 4",
+ "queueMicrotask 5",
+ "queueMicrotask 6",
+ "queueMicrotask 7",
+ "queueMicrotask 8",
+ "queueMicrotask 9",
+ "queueMicrotask 10",
+ "queueMicrotask 11",
+ "queueMicrotask 12",
+ "queueMicrotask 13",
+ "queueMicrotask 14",
+ "queueMicrotask 15",
+ "queueMicrotask 16",
+ "queueMicrotask 17",
+ "queueMicrotask 18",
+ "queueMicrotask 19",
+ "queueMicrotask 20",
+ "queueMicrotask 21",
+ "queueMicrotask 22",
+ "queueMicrotask 23",
+ "queueMicrotask 24",
+ "queueMicrotask 25",
+ "queueMicrotask 26",
+ "queueMicrotask 27",
+ "queueMicrotask 28",
+ "queueMicrotask 29",
+ "queueMicrotask 30",
+ "queueMicrotask 31",
+ "queueMicrotask 32",
+ "queueMicrotask 33",
+ "queueMicrotask 34",
+ "queueMicrotask 35",
+ "queueMicrotask 36",
+ "queueMicrotask 37",
+ "queueMicrotask 38",
+ "queueMicrotask 39",
+ "queueMicrotask 40",
+ "queueMicrotask 41",
+ "queueMicrotask 42",
+ "queueMicrotask 43",
+ "queueMicrotask 44",
+ "queueMicrotask 45",
+ "queueMicrotask 46",
+ "queueMicrotask 47",
+ "queueMicrotask 48",
+ "queueMicrotask 49",
+ "queueMicrotask 50",
+ "queueMicrotask 51",
+ "queueMicrotask 52",
+ "queueMicrotask 53",
+ "queueMicrotask 54",
+ "queueMicrotask 55",
+ "queueMicrotask 56",
+ "queueMicrotask 57",
+ "queueMicrotask 58",
+ "queueMicrotask 59",
+ "queueMicrotask 60",
+ "queueMicrotask 61",
+ "queueMicrotask 62",
+ "queueMicrotask 63",
+ "queueMicrotask 64",
+ "queueMicrotask 65",
+ "queueMicrotask 66",
+ "queueMicrotask 67",
+ "queueMicrotask 68",
+ "queueMicrotask 69",
+ "queueMicrotask 70",
+ "queueMicrotask 71",
+ "queueMicrotask 72",
+ "queueMicrotask 73",
+ "queueMicrotask 74",
+ "queueMicrotask 75",
+ "queueMicrotask 76",
+ "queueMicrotask 77",
+ "queueMicrotask 78",
+ "queueMicrotask 79",
+ "queueMicrotask 80",
+ "queueMicrotask 81",
+ "queueMicrotask 82",
+ "queueMicrotask 83",
+ "queueMicrotask 84",
+ "queueMicrotask 85",
+ "queueMicrotask 86",
+ "queueMicrotask 87",
+ "queueMicrotask 88",
+ "queueMicrotask 89",
+ "queueMicrotask 90",
+ "queueMicrotask 91",
+ "queueMicrotask 92",
+ "queueMicrotask 93",
+ "queueMicrotask 94",
+ "queueMicrotask 95",
+ "queueMicrotask 96",
+ "queueMicrotask 97",
+ "queueMicrotask 98",
+ "queueMicrotask 99",
+ "queueMicrotask 100",
+ "queueMicrotask 101",
+ "queueMicrotask 102",
+ "queueMicrotask 103",
+ "queueMicrotask 104",
+ "queueMicrotask 105",
+ "queueMicrotask 106",
+ "queueMicrotask 107",
+ "queueMicrotask 108",
+ "queueMicrotask 109",
+ "queueMicrotask 110",
+ "queueMicrotask 111",
+ "queueMicrotask 112",
+ "queueMicrotask 113",
+ "queueMicrotask 114",
+ "queueMicrotask 115",
+ "queueMicrotask 116",
+ "queueMicrotask 117",
+ "queueMicrotask 118",
+ "queueMicrotask 119",
+ "queueMicrotask 120",
+ "queueMicrotask 121",
+ "queueMicrotask 122",
+ "queueMicrotask 123",
+ "queueMicrotask 124",
+ "queueMicrotask 125",
+ "queueMicrotask 126",
+ "queueMicrotask 127",
+ "queueMicrotask 128",
+ "queueMicrotask 129",
+ "queueMicrotask 130",
+ "queueMicrotask 131",
+ "queueMicrotask 132",
+ "queueMicrotask 133",
+ "queueMicrotask 134",
+ "queueMicrotask 135",
+ "queueMicrotask 136",
+ "queueMicrotask 137",
+ "queueMicrotask 138",
+ "queueMicrotask 139",
+ "queueMicrotask 140",
+ "queueMicrotask 141",
+ "queueMicrotask 142",
+ "queueMicrotask 143",
+ "queueMicrotask 144",
+ "queueMicrotask 145",
+ "queueMicrotask 146",
+ "queueMicrotask 147",
+ "queueMicrotask 148",
+ "queueMicrotask 149",
+ "queueMicrotask 150",
+ "queueMicrotask 151",
+ "queueMicrotask 152",
+ "queueMicrotask 153",
+ "queueMicrotask 154",
+ "queueMicrotask 155",
+ "queueMicrotask 156",
+ "queueMicrotask 157",
+ "queueMicrotask 158",
+ "queueMicrotask 159",
+ "queueMicrotask 160",
+ "queueMicrotask 161",
+ "queueMicrotask 162",
+ "queueMicrotask 163",
+ "queueMicrotask 164",
+ "queueMicrotask 165",
+ "queueMicrotask 166",
+ "queueMicrotask 167",
+ "queueMicrotask 168",
+ "queueMicrotask 169",
+ "queueMicrotask 170",
+ "queueMicrotask 171",
+ "queueMicrotask 172",
+ "queueMicrotask 173",
+ "queueMicrotask 174",
+ "queueMicrotask 175",
+ "queueMicrotask 176",
+ "queueMicrotask 177",
+ "queueMicrotask 178",
+ "queueMicrotask 179",
+ "queueMicrotask 180",
+ "queueMicrotask 181",
+ "queueMicrotask 182",
+ "queueMicrotask 183",
+ "queueMicrotask 184",
+ "queueMicrotask 185",
+ "queueMicrotask 186",
+ "queueMicrotask 187",
+ "queueMicrotask 188",
+ "queueMicrotask 189",
+ "queueMicrotask 190",
+ "queueMicrotask 191",
+ "queueMicrotask 192",
+ "queueMicrotask 193",
+ "queueMicrotask 194",
+ "queueMicrotask 195",
+ "queueMicrotask 196",
+ "queueMicrotask 197",
+ "queueMicrotask 198",
+ "queueMicrotask 199",
+ ],
+ "runs": [
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "nextTick",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ "queueMicrotask",
+ ],
+ });
+});
+
+it("process.nextTick can be called 100,000 times", async () => {
+ var county = 0;
+ function ticky() {
+ county++;
+ }
+ for (let i = 0; i < 100_000; i++) {
+ process.nextTick(ticky);
+ }
+
+ await 1;
+ expect(county).toBe(100_000);
+});
+
+it("process.nextTick works more than once", async () => {
+ var county = 0;
+ function ticky() {
+ county++;
+ }
+ for (let i = 0; i < 1000; i++) {
+ process.nextTick(ticky);
+ await 1;
+ }
+ expect(county).toBe(1);
+ await new Promise(resolve => setTimeout(resolve, 0));
+ expect(county).toBe(1000);
+});
+
+// `enterWith` is problematic because it and `nextTick` both rely on
+// JSC's `global.onEachMicrotaskTick`, and this test is designed to
+// cover what happens when both are active
+it("process.nextTick and AsyncLocalStorage.enterWith don't conflict", async () => {
+ const AsyncLocalStorage = require("async_hooks").AsyncLocalStorage;
+ const t = require("timers/promises");
+ const storage = new AsyncLocalStorage();
+
+ let call1 = false;
+ let call2 = false;
+
+ process.nextTick(() => (call1 = true));
+
+ const p = Promise.withResolvers();
+ const p2 = p.promise.then(() => {
+ return storage.getStore(); // should not leak "hello"
+ });
+ const promise = Promise.resolve().then(async () => {
+ storage.enterWith("hello");
+ process.nextTick(() => (call2 = true));
+
+ let didCall = false;
+ let value = null;
+ function ticky() {
+ didCall = true;
+ value = storage.getStore();
+ }
+ process.nextTick(ticky);
+ await t.setTimeout(1);
+ expect(didCall).toBe(true);
+ expect(value).toBe("hello");
+ expect(storage.getStore()).toBe("hello");
+ });
+
+ expect(storage.getStore()).toBe(undefined);
+ await promise;
+ p.resolve();
+ expect(await p2).toBe(undefined);
+
+ expect(call1).toBe(true);
+ expect(call2).toBe(true);
+});
diff --git a/test/js/node/readline/readline_promises.node.test.ts b/test/js/node/readline/readline_promises.node.test.ts
index a46fe841f..a6c2fcef2 100644
--- a/test/js/node/readline/readline_promises.node.test.ts
+++ b/test/js/node/readline/readline_promises.node.test.ts
@@ -40,7 +40,7 @@ describe("readline/promises.createInterface()", () => {
rli.on("line", mustNotCall());
fi.emit("data", "\t");
- process.nextTick(() => {
+ queueMicrotask(() => {
expect(fi.output).toMatch(/^Tab completion error/);
rli.close();
done();