diff --git a/content/services/auction_worklet/context_recycler.cc b/content/services/auction_worklet/context_recycler.cc index 52df3d9003abc5..fd99509083b6ec 100644 --- a/content/services/auction_worklet/context_recycler.cc +++ b/content/services/auction_worklet/context_recycler.cc @@ -24,6 +24,8 @@ #include "content/services/auction_worklet/set_priority_signals_override_bindings.h" #include "content/services/auction_worklet/shared_storage_bindings.h" #include "v8/include/v8-context.h" +#include "v8/include/v8-isolate.h" +#include "v8/include/v8-microtask-queue.h" namespace auction_worklet { @@ -38,7 +40,23 @@ PersistedLazyFiller::PersistedLazyFiller(AuctionV8Helper* v8_helper) ContextRecycler::ContextRecycler(AuctionV8Helper* v8_helper) : v8_helper_(v8_helper) {} -ContextRecycler::~ContextRecycler() = default; +ContextRecycler::~ContextRecycler() { + // All worklet contexts share the isolate's default microtask queue (kAuto + // policy). If the most recent script invocation was terminated by the + // off-thread watchdog, ~CallDepthScope will have skipped the kAuto + // checkpoint while `is_execution_terminating()` was still true, leaving any + // microtasks the script enqueued stranded in the shared queue. Those + // microtasks can hold references (via v8::External) to the Bindings / + // LazyFillers we are about to destroy. Drain them now while everything is + // still alive so a later worklet call on the same isolate doesn't run them + // against freed memory. + v8::Isolate* isolate = v8_helper_->isolate(); + if (!context_.IsEmpty() && v8::Isolate::TryGetCurrent() == isolate) { + v8::HandleScope handle_scope(isolate); + v8::Context::Scope context_scope(context_.Get(isolate)); + v8::MicrotasksScope::PerformCheckpoint(isolate); + } +} void ContextRecycler::AddForDebuggingOnlyBindings() { DCHECK(!for_debugging_only_bindings_);