Commit 734a6575 authored by tzik's avatar tzik Committed by Commit Bot

Do not enqueue or run a microtask on detached contexts

This CL disables EnqueueMicrotask and RunMicrotasks on detached
contexts. That is, if an embedder call DetachGlobal() on a v8::Context,
EnqueueMicrotask on that context will not take effect, and all Microtask
that is enqueued before DetachGlobal will be cancelled.

On Blink, this implies that a frame will no longer run a microtask after
it's navigated away. OTOH, detached frames in Blink are not affected.

Bug: v8:8124
Change-Id: I5b00ceef5ea2afb87cf067a65eb95c29bf91176d
Reviewed-on: https://chromium-review.googlesource.com/c/1416071Reviewed-by: 's avatarToon Verwaest <verwaest@chromium.org>
Reviewed-by: 's avatarYang Guo <yangguo@chromium.org>
Reviewed-by: 's avatarBenedikt Meurer <bmeurer@chromium.org>
Reviewed-by: 's avatarAdam Klein <adamk@chromium.org>
Commit-Queue: Taiju Tsuiki <tzik@chromium.org>
Cr-Commit-Position: refs/heads/master@{#59445}
parent a9174cf0
......@@ -353,6 +353,8 @@ void Bootstrapper::DetachGlobal(Handle<Context> env) {
if (FLAG_track_detached_contexts) {
isolate_->AddDetachedContext(env);
}
env->native_context()->set_microtask_queue(nullptr);
}
namespace {
......
......@@ -34,7 +34,7 @@ class MicrotaskQueueBuiltinsAssembler : public CodeStubAssembler {
TNode<IntPtrT> start,
TNode<IntPtrT> index);
void PrepareForContext(TNode<Context> microtask_context);
void PrepareForContext(TNode<Context> microtask_context, Label* bailout);
void RunSingleMicrotask(TNode<Context> current_context,
TNode<Microtask> microtask);
void IncrementFinishedMicrotaskCount(TNode<RawPtrT> microtask_queue);
......@@ -104,8 +104,13 @@ TNode<IntPtrT> MicrotaskQueueBuiltinsAssembler::CalculateRingBufferOffset(
}
void MicrotaskQueueBuiltinsAssembler::PrepareForContext(
TNode<Context> native_context) {
TNode<Context> native_context, Label* bailout) {
CSA_ASSERT(this, IsNativeContext(native_context));
// Skip the microtask execution if the associated context is shutdown.
GotoIf(WordEqual(GetMicrotaskQueue(native_context), IntPtrConstant(0)),
bailout);
EnterMicrotaskContext(native_context);
SetCurrentContext(native_context);
}
......@@ -150,7 +155,7 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask(
TNode<Context> microtask_context =
LoadObjectField<Context>(microtask, CallableTask::kContextOffset);
TNode<Context> native_context = LoadNativeContext(microtask_context);
PrepareForContext(native_context);
PrepareForContext(native_context, &done);
TNode<JSReceiver> callable =
LoadObjectField<JSReceiver>(microtask, CallableTask::kCallableOffset);
......@@ -193,7 +198,7 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask(
TNode<Context> microtask_context = LoadObjectField<Context>(
microtask, PromiseResolveThenableJobTask::kContextOffset);
TNode<Context> native_context = LoadNativeContext(microtask_context);
PrepareForContext(native_context);
PrepareForContext(native_context, &done);
Node* const promise_to_resolve = LoadObjectField(
microtask, PromiseResolveThenableJobTask::kPromiseToResolveOffset);
......@@ -217,7 +222,7 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask(
TNode<Context> microtask_context = LoadObjectField<Context>(
microtask, PromiseReactionJobTask::kContextOffset);
TNode<Context> native_context = LoadNativeContext(microtask_context);
PrepareForContext(native_context);
PrepareForContext(native_context, &done);
Node* const argument =
LoadObjectField(microtask, PromiseReactionJobTask::kArgumentOffset);
......@@ -250,7 +255,7 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask(
TNode<Context> microtask_context = LoadObjectField<Context>(
microtask, PromiseReactionJobTask::kContextOffset);
TNode<Context> native_context = LoadNativeContext(microtask_context);
PrepareForContext(native_context);
PrepareForContext(native_context, &done);
Node* const argument =
LoadObjectField(microtask, PromiseReactionJobTask::kArgumentOffset);
......@@ -286,7 +291,7 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask(
FinalizationGroupCleanupJobTask::kFinalizationGroupOffset);
TNode<Context> native_context = LoadObjectField<Context>(
finalization_group, JSFinalizationGroup::kNativeContextOffset);
PrepareForContext(native_context);
PrepareForContext(native_context, &done);
Node* const result = CallRuntime(Runtime::kFinalizationGroupCleanupJob,
native_context, finalization_group);
......@@ -474,6 +479,11 @@ TF_BUILTIN(EnqueueMicrotask, MicrotaskQueueBuiltinsAssembler) {
TNode<Context> native_context = LoadNativeContext(context);
TNode<RawPtrT> microtask_queue = GetMicrotaskQueue(native_context);
// Do not store the microtask if MicrotaskQueue is not available, that may
// happen when the context shutdown.
Label if_shutdown(this, Label::kDeferred);
GotoIf(WordEqual(microtask_queue, IntPtrConstant(0)), &if_shutdown);
TNode<RawPtrT> ring_buffer = GetMicrotaskRingBuffer(microtask_queue);
TNode<IntPtrT> capacity = GetMicrotaskQueueCapacity(microtask_queue);
TNode<IntPtrT> size = GetMicrotaskQueueSize(microtask_queue);
......@@ -506,6 +516,9 @@ TF_BUILTIN(EnqueueMicrotask, MicrotaskQueueBuiltinsAssembler) {
isolate_constant, microtask_queue, microtask);
Return(UndefinedConstant());
}
Bind(&if_shutdown);
Return(UndefinedConstant());
}
TF_BUILTIN(RunMicrotasks, MicrotaskQueueBuiltinsAssembler) {
......
......@@ -941,7 +941,7 @@ class Object {
#ifdef OBJECT_PRINT
// For our gdb macros, we should perhaps change these in the future.
void Print() const;
V8_EXPORT_PRIVATE void Print() const;
// Prints this object with details.
void Print(std::ostream& os) const; // NOLINT
......
......@@ -10,7 +10,9 @@
#include <vector>
#include "src/heap/factory.h"
#include "src/objects-inl.h"
#include "src/objects/foreign.h"
#include "src/objects/js-array-inl.h"
#include "src/visitors.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
......@@ -25,7 +27,29 @@ void RunStdFunction(void* data) {
(*f)();
}
class MicrotaskQueueTest : public TestWithNativeContext {
template <typename TMixin>
class WithFinalizationGroupMixin : public TMixin {
public:
WithFinalizationGroupMixin() {
FLAG_harmony_weak_refs = true;
FLAG_expose_gc = true;
}
private:
SaveFlags save_flags_;
DISALLOW_COPY_AND_ASSIGN(WithFinalizationGroupMixin);
};
using TestWithNativeContextAndFinalizationGroup = //
WithInternalIsolateMixin< //
WithContextMixin< //
WithFinalizationGroupMixin< //
WithIsolateScopeMixin< //
WithSharedIsolateMixin< //
::testing::Test>>>>>;
class MicrotaskQueueTest : public TestWithNativeContextAndFinalizationGroup {
public:
template <typename F>
Handle<Microtask> NewMicrotask(F&& f) {
......@@ -185,5 +209,114 @@ TEST_F(MicrotaskQueueTest, VisitRoot) {
EXPECT_EQ(expected, actual);
}
TEST_F(MicrotaskQueueTest, DetachGlobal_Enqueue) {
EXPECT_EQ(0, microtask_queue()->size());
// Detach MicrotaskQueue from the current context.
context()->DetachGlobal();
// No microtask should be enqueued after DetachGlobal call.
EXPECT_EQ(0, microtask_queue()->size());
RunJS("Promise.resolve().then(()=>{})");
EXPECT_EQ(0, microtask_queue()->size());
}
TEST_F(MicrotaskQueueTest, DetachGlobal_Run) {
EXPECT_EQ(0, microtask_queue()->size());
// Enqueue microtasks to the current context.
Handle<JSArray> ran = RunJS<JSArray>(
"var ran = [false, false, false, false];"
"Promise.resolve().then(() => { ran[0] = true; });"
"Promise.reject().catch(() => { ran[1] = true; });"
"ran");
Handle<JSFunction> function =
RunJS<JSFunction>("(function() { ran[2] = true; })");
Handle<CallableTask> callable =
factory()->NewCallableTask(function, Utils::OpenHandle(*context()));
microtask_queue()->EnqueueMicrotask(*callable);
// The handler should not run at this point.
const int kNumExpectedTasks = 3;
for (int i = 0; i < kNumExpectedTasks; ++i) {
EXPECT_TRUE(
Object::GetElement(isolate(), ran, i).ToHandleChecked()->IsFalse());
}
EXPECT_EQ(kNumExpectedTasks, microtask_queue()->size());
// Detach MicrotaskQueue from the current context.
context()->DetachGlobal();
// RunMicrotasks processes pending Microtasks, but Microtasks that are
// associated to a detached context should be cancelled and should not take
// effect.
microtask_queue()->RunMicrotasks(isolate());
EXPECT_EQ(0, microtask_queue()->size());
for (int i = 0; i < kNumExpectedTasks; ++i) {
EXPECT_TRUE(
Object::GetElement(isolate(), ran, i).ToHandleChecked()->IsFalse());
}
}
TEST_F(MicrotaskQueueTest, DetachGlobal_FinalizationGroup) {
// Enqueue an FinalizationGroupCleanupTask.
Handle<JSArray> ran = RunJS<JSArray>(
"var ran = [false];"
"var wf = new FinalizationGroup(() => { ran[0] = true; });"
"(function() { wf.register({}, {}); })();"
"gc();"
"ran");
EXPECT_TRUE(
Object::GetElement(isolate(), ran, 0).ToHandleChecked()->IsFalse());
EXPECT_EQ(1, microtask_queue()->size());
// Detach MicrotaskQueue from the current context.
context()->DetachGlobal();
microtask_queue()->RunMicrotasks(isolate());
// RunMicrotasks processes the pending Microtask, but Microtasks that are
// associated to a detached context should be cancelled and should not take
// effect.
EXPECT_EQ(0, microtask_queue()->size());
EXPECT_TRUE(
Object::GetElement(isolate(), ran, 0).ToHandleChecked()->IsFalse());
}
namespace {
void DummyPromiseHook(PromiseHookType type, Local<Promise> promise,
Local<Value> parent) {}
} // namespace
TEST_F(MicrotaskQueueTest, DetachGlobal_PromiseResolveThenableJobTask) {
// Use a PromiseHook to switch the implementation to ResolvePromise runtime,
// instead of ResolvePromise builtin.
v8_isolate()->SetPromiseHook(&DummyPromiseHook);
RunJS(
"var resolve;"
"var promise = new Promise(r => { resolve = r; });"
"promise.then(() => {});"
"resolve({});");
// A PromiseResolveThenableJobTask is pending in the MicrotaskQueue.
EXPECT_EQ(1, microtask_queue()->size());
// Detach MicrotaskQueue from the current context.
context()->DetachGlobal();
// RunMicrotasks processes the pending Microtask, but Microtasks that are
// associated to a detached context should be cancelled and should not take
// effect.
// As PromiseResolveThenableJobTask queues another task for resolution,
// the return value is 2 if it ran.
EXPECT_EQ(1, microtask_queue()->RunMicrotasks(isolate()));
EXPECT_EQ(0, microtask_queue()->size());
}
} // namespace internal
} // namespace v8
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment