refactor: Node.js temporary "explicit" microtask policy scope pattern (#46973)

refactor: Node.js explicit microtask scope pattern
This commit is contained in:
Calvin 2025-05-07 19:21:39 -06:00 committed by GitHub
commit 580fa57a29
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 60 additions and 44 deletions

View file

@ -908,28 +908,22 @@ void NodeBindings::UvRunOnce() {
// Enter node context while dealing with uv events. // Enter node context while dealing with uv events.
v8::Context::Scope context_scope(env->context()); v8::Context::Scope context_scope(env->context());
// Node.js expects `kExplicit` microtasks policy and will run microtasks {
// checkpoints after every call into JavaScript. Since we use a different util::ExplicitMicrotasksScope microtasks_scope(
// policy in the renderer - switch to `kExplicit` and then drop back to the env->context()->GetMicrotaskQueue());
// previous policy value.
v8::MicrotaskQueue* microtask_queue = env->context()->GetMicrotaskQueue();
auto old_policy = microtask_queue->microtasks_policy();
DCHECK_EQ(microtask_queue->GetMicrotasksScopeDepth(), 0);
microtask_queue->set_microtasks_policy(v8::MicrotasksPolicy::kExplicit);
if (browser_env_ != BrowserEnvironment::kBrowser) if (browser_env_ != BrowserEnvironment::kBrowser)
TRACE_EVENT_BEGIN0("devtools.timeline", "FunctionCall"); TRACE_EVENT_BEGIN0("devtools.timeline", "FunctionCall");
// Deal with uv events. // Deal with uv events.
int r = uv_run(uv_loop_, UV_RUN_NOWAIT); int r = uv_run(uv_loop_, UV_RUN_NOWAIT);
if (browser_env_ != BrowserEnvironment::kBrowser) if (browser_env_ != BrowserEnvironment::kBrowser)
TRACE_EVENT_END0("devtools.timeline", "FunctionCall"); TRACE_EVENT_END0("devtools.timeline", "FunctionCall");
microtask_queue->set_microtasks_policy(old_policy); if (r == 0)
base::RunLoop().QuitWhenIdle(); // Quit from uv.
if (r == 0) }
base::RunLoop().QuitWhenIdle(); // Quit from uv.
// Tell the worker thread to continue polling. // Tell the worker thread to continue polling.
uv_sem_post(&embed_sem_); uv_sem_post(&embed_sem_);

View file

@ -130,6 +130,16 @@ node::Environment* CreateEnvironment(v8::Isolate* isolate,
return env; return env;
} }
ExplicitMicrotasksScope::ExplicitMicrotasksScope(v8::MicrotaskQueue* queue)
: microtask_queue_(queue), original_policy_(queue->microtasks_policy()) {
DCHECK_EQ(microtask_queue_->GetMicrotasksScopeDepth(), 0);
microtask_queue_->set_microtasks_policy(v8::MicrotasksPolicy::kExplicit);
}
ExplicitMicrotasksScope::~ExplicitMicrotasksScope() {
microtask_queue_->set_microtasks_policy(original_policy_);
}
} // namespace electron::util } // namespace electron::util
namespace electron::Buffer { namespace electron::Buffer {

View file

@ -10,6 +10,8 @@
#include <vector> #include <vector>
#include "base/containers/span.h" #include "base/containers/span.h"
#include "base/memory/raw_ptr.h"
#include "v8-microtask-queue.h"
#include "v8/include/v8-forward.h" #include "v8/include/v8-forward.h"
namespace node { namespace node {
@ -63,6 +65,27 @@ node::Environment* CreateEnvironment(v8::Isolate* isolate,
node::EnvironmentFlags::Flags env_flags, node::EnvironmentFlags::Flags env_flags,
std::string_view process_type = ""); std::string_view process_type = "");
// A scope that temporarily changes the microtask policy to explicit. Use this
// anywhere that can trigger Node.js or uv_run().
//
// Node.js expects `kExplicit` microtasks policy and will run microtasks
// checkpoints after every call into JavaScript. Since we use a different
// policy in the renderer, this scope temporarily changes the policy to
// `kExplicit` while the scope is active, then restores the original policy
// when it's destroyed.
class ExplicitMicrotasksScope {
public:
explicit ExplicitMicrotasksScope(v8::MicrotaskQueue* queue);
~ExplicitMicrotasksScope();
ExplicitMicrotasksScope(const ExplicitMicrotasksScope&) = delete;
ExplicitMicrotasksScope& operator=(const ExplicitMicrotasksScope&) = delete;
private:
base::raw_ptr<v8::MicrotaskQueue> microtask_queue_;
v8::MicrotasksPolicy original_policy_;
};
} // namespace electron::util } // namespace electron::util
namespace electron::Buffer { namespace electron::Buffer {

View file

@ -16,6 +16,7 @@
#include "shell/common/gin_helper/event_emitter_caller.h" #include "shell/common/gin_helper/event_emitter_caller.h"
#include "shell/common/node_bindings.h" #include "shell/common/node_bindings.h"
#include "shell/common/node_includes.h" #include "shell/common/node_includes.h"
#include "shell/common/node_util.h"
#include "shell/common/options_switches.h" #include "shell/common/options_switches.h"
#include "shell/renderer/electron_render_frame_observer.h" #include "shell/renderer/electron_render_frame_observer.h"
#include "shell/renderer/web_worker_observer.h" #include "shell/renderer/web_worker_observer.h"
@ -178,19 +179,12 @@ void ElectronRendererClient::WillReleaseScriptContext(
if (env == node_bindings_->uv_env()) if (env == node_bindings_->uv_env())
node_bindings_->set_uv_env(nullptr); node_bindings_->set_uv_env(nullptr);
// Destroying the node environment will also run the uv loop, // Destroying the node environment will also run the uv loop.
// Node.js expects `kExplicit` microtasks policy and will run microtasks {
// checkpoints after every call into JavaScript. Since we use a different util::ExplicitMicrotasksScope microtasks_scope(
// policy in the renderer - switch to `kExplicit` and then drop back to the context->GetMicrotaskQueue());
// previous policy value. environments_.erase(iter);
v8::MicrotaskQueue* microtask_queue = context->GetMicrotaskQueue(); }
auto old_policy = microtask_queue->microtasks_policy();
DCHECK_EQ(microtask_queue->GetMicrotasksScopeDepth(), 0);
microtask_queue->set_microtasks_policy(v8::MicrotasksPolicy::kExplicit);
environments_.erase(iter);
microtask_queue->set_microtasks_policy(old_policy);
// ElectronBindings is tracking node environments. // ElectronBindings is tracking node environments.
electron_bindings_->EnvironmentDestroyed(env); electron_bindings_->EnvironmentDestroyed(env);

View file

@ -14,6 +14,7 @@
#include "shell/common/gin_helper/event_emitter_caller.h" #include "shell/common/gin_helper/event_emitter_caller.h"
#include "shell/common/node_bindings.h" #include "shell/common/node_bindings.h"
#include "shell/common/node_includes.h" #include "shell/common/node_includes.h"
#include "shell/common/node_util.h"
namespace electron { namespace electron {
@ -112,19 +113,13 @@ void WebWorkerObserver::ContextWillDestroy(v8::Local<v8::Context> context) {
gin_helper::EmitEvent(env->isolate(), env->process_object(), "exit"); gin_helper::EmitEvent(env->isolate(), env->process_object(), "exit");
} }
// Destroying the node environment will also run the uv loop, // Destroying the node environment will also run the uv loop.
// Node.js expects `kExplicit` microtasks policy and will run microtasks {
// checkpoints after every call into JavaScript. Since we use a different util::ExplicitMicrotasksScope microtasks_scope(
// policy in the renderer - switch to `kExplicit` context->GetMicrotaskQueue());
v8::MicrotaskQueue* microtask_queue = context->GetMicrotaskQueue(); base::EraseIf(environments_,
auto old_policy = microtask_queue->microtasks_policy(); [env](auto const& item) { return item.get() == env; });
DCHECK_EQ(microtask_queue->GetMicrotasksScopeDepth(), 0); }
microtask_queue->set_microtasks_policy(v8::MicrotasksPolicy::kExplicit);
base::EraseIf(environments_,
[env](auto const& item) { return item.get() == env; });
microtask_queue->set_microtasks_policy(old_policy);
// ElectronBindings is tracking node environments. // ElectronBindings is tracking node environments.
electron_bindings_->EnvironmentDestroyed(env); electron_bindings_->EnvironmentDestroyed(env);