diff --git a/deps/v8/include/v8-microtask-queue.h b/deps/v8/include/v8-microtask-queue.h index af9caa54a8ff..6e0868916a7f 100644 --- a/deps/v8/include/v8-microtask-queue.h +++ b/deps/v8/include/v8-microtask-queue.h @@ -97,6 +97,28 @@ class V8_EXPORT MicrotaskQueue { */ virtual int GetMicrotasksScopeDepth() const = 0; + /** + * Qualia: the bookkeeping that says "a checkpoint is in progress on this + * queue". V8 assumes the checkpoint runs to completion on one native stack; + * node-fibers can suspend a fiber mid-checkpoint, so it saves and restores + * this state per fiber on every switch. + */ + struct RunState { + bool is_running = false; + int scope_depth = 0; + int suppressions = 0; + // Set when a fiber is suspended mid-checkpoint: once it resumes, its drain + // loop stops after the current microtask (another stack has taken over). + bool stop_draining = false; + }; + RunState GetRunState() const; + void SetRunState(const RunState& state); + + /** + * Qualia: number of microtasks currently queued. + */ + size_t Size() const; + MicrotaskQueue(const MicrotaskQueue&) = delete; MicrotaskQueue& operator=(const MicrotaskQueue&) = delete; diff --git a/deps/v8/src/api/api.cc b/deps/v8/src/api/api.cc index 8d224bea2e22..1d7407616040 100644 --- a/deps/v8/src/api/api.cc +++ b/deps/v8/src/api/api.cc @@ -9555,6 +9555,18 @@ std::unique_ptr MicrotaskQueue::New(Isolate* isolate, return ret; } +MicrotaskQueue::RunState MicrotaskQueue::GetRunState() const { + return static_cast(this)->GetInternalRunState(); +} + +void MicrotaskQueue::SetRunState(const RunState& state) { + static_cast(this)->SetInternalRunState(state); +} + +size_t MicrotaskQueue::Size() const { + return static_cast(static_cast(this)->size()); +} + MicrotasksScope::MicrotasksScope(Isolate* isolate, MicrotasksScope::Type type) : MicrotasksScope(isolate, nullptr, type) {} diff --git a/deps/v8/src/builtins/builtins-microtask-queue-gen.cc b/deps/v8/src/builtins/builtins-microtask-queue-gen.cc index 6b0c63b34ff0..576d9edc69f8 100644 --- a/deps/v8/src/builtins/builtins-microtask-queue-gen.cc +++ b/deps/v8/src/builtins/builtins-microtask-queue-gen.cc @@ -597,6 +597,15 @@ TF_BUILTIN(RunMicrotasks, MicrotaskQueueBuiltinsAssembler) { Goto(&loop); BIND(&loop); + // Qualia: exit if this stack was suspended mid-drain and has since been + // resumed; another stack took over draining the queue. + GotoIf(WordNotEqual( + Load(microtask_queue, + IntPtrConstant( + MicrotaskQueue::kQualiaStopDrainingOffset)), + IntPtrConstant(0)), + &done); + TNode size = GetMicrotaskQueueSize(microtask_queue); // Exit if the queue is empty. diff --git a/deps/v8/src/execution/microtask-queue.cc b/deps/v8/src/execution/microtask-queue.cc index 12a626900d02..8caf38a1a36a 100644 --- a/deps/v8/src/execution/microtask-queue.cc +++ b/deps/v8/src/execution/microtask-queue.cc @@ -25,6 +25,8 @@ const size_t MicrotaskQueue::kCapacityOffset = OFFSET_OF(MicrotaskQueue, capacity_); const size_t MicrotaskQueue::kSizeOffset = OFFSET_OF(MicrotaskQueue, size_); const size_t MicrotaskQueue::kStartOffset = OFFSET_OF(MicrotaskQueue, start_); +const size_t MicrotaskQueue::kQualiaStopDrainingOffset = + OFFSET_OF(MicrotaskQueue, qualia_stop_draining_); const size_t MicrotaskQueue::kFinishedMicrotaskCountOffset = OFFSET_OF(MicrotaskQueue, finished_microtask_count_); @@ -174,6 +176,9 @@ int MicrotaskQueue::RunMicrotasks(Isolate* isolate) { &maybe_exception); processed_microtask_count = static_cast(finished_microtask_count_ - base_count); + // Qualia: the loop may have stopped early for a resumed fiber; the + // stack that took over the drain owns whatever is left. + qualia_stop_draining_ = 0; } TRACE_EVENT_END1("v8.execute", "RunMicrotasks", "microtask_count", processed_microtask_count); @@ -191,7 +196,6 @@ int MicrotaskQueue::RunMicrotasks(Isolate* isolate) { OnCompleted(isolate); return -1; } - DCHECK_EQ(0, size()); OnCompleted(isolate); return processed_microtask_count; diff --git a/deps/v8/src/execution/microtask-queue.h b/deps/v8/src/execution/microtask-queue.h index 6091fa3575cf..c4c6129bdf25 100644 --- a/deps/v8/src/execution/microtask-queue.h +++ b/deps/v8/src/execution/microtask-queue.h @@ -100,6 +100,22 @@ class V8_EXPORT_PRIVATE MicrotaskQueue final : public v8::MicrotaskQueue { intptr_t size() const { return size_; } intptr_t start() const { return start_; } + // Qualia: see v8::MicrotaskQueue::RunState. + v8::MicrotaskQueue::RunState GetInternalRunState() const { + v8::MicrotaskQueue::RunState state; + state.is_running = is_running_microtasks_; + state.scope_depth = microtasks_depth_; + state.suppressions = microtasks_suppressions_; + state.stop_draining = qualia_stop_draining_ != 0; + return state; + } + void SetInternalRunState(const v8::MicrotaskQueue::RunState& state) { + is_running_microtasks_ = state.is_running; + microtasks_depth_ = state.scope_depth; + microtasks_suppressions_ = state.suppressions; + qualia_stop_draining_ = state.stop_draining ? 1 : 0; + } + Microtask get(intptr_t index) const; MicrotaskQueue* next() const { return next_; } @@ -110,6 +126,7 @@ class V8_EXPORT_PRIVATE MicrotaskQueue final : public v8::MicrotaskQueue { static const size_t kSizeOffset; static const size_t kStartOffset; static const size_t kFinishedMicrotaskCountOffset; + static const size_t kQualiaStopDrainingOffset; static const intptr_t kMinimumCapacity; @@ -146,6 +163,9 @@ class V8_EXPORT_PRIVATE MicrotaskQueue final : public v8::MicrotaskQueue { v8::MicrotasksPolicy microtasks_policy_ = v8::MicrotasksPolicy::kAuto; bool is_running_microtasks_ = false; + + // Qualia: checked by the RunMicrotasks builtin before each microtask. + intptr_t qualia_stop_draining_ = 0; using CallbackWithData = std::pair; std::vector microtasks_completed_callbacks_; diff --git a/src/api/callback.cc b/src/api/callback.cc index 3a8bd9155a85..526211cacec1 100644 --- a/src/api/callback.cc +++ b/src/api/callback.cc @@ -1,6 +1,7 @@ #include "node.h" #include "async_wrap-inl.h" #include "env-inl.h" +#include "node_internals.h" #include "v8.h" namespace node { @@ -16,6 +17,20 @@ using v8::Object; using v8::String; using v8::Value; +typedef void (*QualiaMicrotaskCheckpointHook)(Isolate* isolate, + Local context, + v8::MicrotaskQueue* queue); +static QualiaMicrotaskCheckpointHook qualia_microtask_checkpoint_hook = nullptr; + +void PerformMicrotaskCheckpoint(Isolate* isolate, Local context) { + v8::MicrotaskQueue* queue = context->GetMicrotaskQueue(); + if (qualia_microtask_checkpoint_hook != nullptr) { + qualia_microtask_checkpoint_hook(isolate, context, queue); + } else { + queue->PerformCheckpoint(isolate); + } +} + CallbackScope::CallbackScope(Isolate* isolate, Local object, async_context async_context) @@ -134,7 +149,7 @@ void InternalCallbackScope::Close() { Local context = env_->context(); if (!tick_info->has_tick_scheduled()) { - context->GetMicrotaskQueue()->PerformCheckpoint(isolate); + PerformMicrotaskCheckpoint(isolate, context); perform_stopping_check(); } @@ -358,3 +373,10 @@ Local MakeCallback(Isolate* isolate, } } // namespace node + +// Qualia: looked up with dlsym() by node-fibers, so addons still load on a +// stock node. Passing nullptr restores the default behaviour. +extern "C" NODE_EXTERN void node_qualia_set_microtask_checkpoint_hook( + node::QualiaMicrotaskCheckpointHook hook) { + node::qualia_microtask_checkpoint_hook = hook; +} diff --git a/src/node_internals.h b/src/node_internals.h index 427cfab4eebc..f76019fcec94 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -88,6 +88,12 @@ std::string FormatCaughtException(v8::Isolate* isolate, const v8::TryCatch& try_catch); void ResetStdio(); // Safe to call more than once and from signal handlers. + +// Qualia: drains a context's microtask queue, through the hook registered with +// node_qualia_set_microtask_checkpoint_hook() if there is one (node-fibers uses +// it to drain on a fiber, so `await` continuations can Fiber.yield()). +void PerformMicrotaskCheckpoint(v8::Isolate* isolate, + v8::Local context); #ifdef __POSIX__ void SignalExit(int signal, siginfo_t* info, void* ucontext); #endif diff --git a/src/node_task_queue.cc b/src/node_task_queue.cc index 5d0e2b0d4c7b..037abe476db2 100644 --- a/src/node_task_queue.cc +++ b/src/node_task_queue.cc @@ -172,7 +172,7 @@ static void EnqueueMicrotask(const FunctionCallbackInfo& args) { static void RunMicrotasks(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - env->context()->GetMicrotaskQueue()->PerformCheckpoint(env->isolate()); + PerformMicrotaskCheckpoint(env->isolate(), env->context()); } static void SetTickCallback(const FunctionCallbackInfo& args) {