From 6212447d40e6d2e8cbcd1f3e25190c3768d14ead Mon Sep 17 00:00:00 2001 From: Jordan Baczuk Date: Fri, 2 Oct 2026 09:07:43 -0600 Subject: [PATCH] feat(v8): let the embedder run queued dispatchable jobs in a batch RunNextDispatchableMicrotask (and the unmangled v8_qualia_RunNextDispatchableMicrotask) takes the next job off the default microtask queue if it is a promise reaction job with CPED set, and runs it on the current stack the way RunDispatchedMicrotask does. An embedder running a dispatched job on a coroutine can use it to keep running the dispatchable jobs queued right behind it, instead of switching back to the main stack and being handed each one separately. The RunMicrotasks loop re-reads the queue's start and size before every job, so the drain it is nested in continues with the job after them. Co-Authored-By: Claude Opus 5.5 --- deps/v8/include/v8-microtask-dispatch.h | 11 +++++++ deps/v8/src/runtime/runtime-promise.cc | 39 +++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/deps/v8/include/v8-microtask-dispatch.h b/deps/v8/include/v8-microtask-dispatch.h index 58a6251f3e3..5557ea581a5 100644 --- a/deps/v8/include/v8-microtask-dispatch.h +++ b/deps/v8/include/v8-microtask-dispatch.h @@ -36,6 +36,16 @@ V8_EXPORT void SetMicrotaskDispatchCallback(Isolate* isolate, V8_EXPORT void RunDispatchedMicrotask(Isolate* isolate, DispatchedMicrotask* task); +// If the next job in the isolate's default microtask queue is a promise +// reaction job with non-undefined CPED, takes it off the queue and runs it on +// the current stack, as RunDispatchedMicrotask does, and returns true. +// Otherwise returns false and leaves the queue alone. Lets an embedder that is +// running a dispatched job keep running the dispatchable jobs right behind it +// without switching stacks for each one. Only call it while the job that was +// dispatched is still nested inside the microtask loop, so the order of jobs +// is unchanged. +V8_EXPORT bool RunNextDispatchableMicrotask(Isolate* isolate); + } // namespace v8 // Unmangled entry points, so an addon can find them with dlsym() and still @@ -45,6 +55,7 @@ V8_EXPORT void v8_qualia_SetMicrotaskDispatchCallback( v8::Isolate* isolate, v8::MicrotaskDispatchCallback callback, void* data); V8_EXPORT void v8_qualia_RunDispatchedMicrotask(v8::Isolate* isolate, v8::DispatchedMicrotask* task); +V8_EXPORT bool v8_qualia_RunNextDispatchableMicrotask(v8::Isolate* isolate); } #endif // INCLUDE_V8_MICROTASK_DISPATCH_H_ diff --git a/deps/v8/src/runtime/runtime-promise.cc b/deps/v8/src/runtime/runtime-promise.cc index 2e7a7b36f57..c33f1b198d6 100644 --- a/deps/v8/src/runtime/runtime-promise.cc +++ b/deps/v8/src/runtime/runtime-promise.cc @@ -13,6 +13,7 @@ #include "src/objects/heap-object-inl.h" #include "src/objects/js-promise-inl.h" #include "src/objects/microtask-inl.h" +#include "src/objects/promise-inl.h" #include "src/objects/objects-inl.h" #include "src/objects/oddball-inl.h" #include "src/runtime/runtime-utils.h" @@ -188,6 +189,40 @@ void RunDispatchedMicrotask(Isolate* v8_isolate, DispatchedMicrotask* task) { } } +bool RunNextDispatchableMicrotask(Isolate* v8_isolate) { + internal::Isolate* isolate = reinterpret_cast(v8_isolate); + internal::MicrotaskQueue* queue = isolate->default_microtask_queue(); + if (queue == nullptr || queue->size() == 0) return false; + internal::HandleScope handle_scope(isolate); + internal::Microtask front = queue->get(0); + if (!front.IsPromiseFulfillReactionJobTask() && + !front.IsPromiseRejectReactionJobTask()) { + return false; + } + if (internal::PromiseReactionJobTask::cast(front) + .continuation_preserved_embedder_data() + .IsUndefined(isolate)) { + return false; + } + internal::Handle microtask(front, isolate); + // Take it off the front of the queue the way the RunMicrotasks builtin does + // (that loop re-reads start and size before every job, so the drain this is + // nested in carries on with the job after it). + internal::Address base = reinterpret_cast(queue); + intptr_t* start = reinterpret_cast( + base + internal::MicrotaskQueue::kStartOffset); + intptr_t* size = reinterpret_cast( + base + internal::MicrotaskQueue::kSizeOffset); + *start = (*start + 1) % queue->capacity(); + *size -= 1; + std::unique_ptr private_queue = + internal::MicrotaskQueue::New(isolate); + private_queue->EnqueueMicrotask(*microtask); + RunDispatchedMicrotask(v8_isolate, + new DispatchedMicrotask{private_queue.release()}); + return true; +} + } // namespace v8 extern "C" { @@ -200,6 +235,10 @@ void v8_qualia_RunDispatchedMicrotask(v8::Isolate* isolate, v8::DispatchedMicrotask* task) { v8::RunDispatchedMicrotask(isolate, task); } + +bool v8_qualia_RunNextDispatchableMicrotask(v8::Isolate* isolate) { + return v8::RunNextDispatchableMicrotask(isolate); +} } namespace v8 {