Commit a32e37ed authored by tzik's avatar tzik Committed by Commit Bot

Reland "Do not enqueue or run a microtask on detached contexts"

This is a reland of 734a6575

Original change's description:
> 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/1416071
> Reviewed-by: Toon Verwaest <verwaest@chromium.org>
> Reviewed-by: Yang Guo <yangguo@chromium.org>
> Reviewed-by: Benedikt Meurer <bmeurer@chromium.org>
> Reviewed-by: Adam Klein <adamk@chromium.org>
> Commit-Queue: Taiju Tsuiki <tzik@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#59445}

Tbr: adamk@chromium.org, yangguo@chromium.org, verwaest@chromium.org
Bug: v8:8124
Change-Id: I959a18ae214f1385d5f453b3ed94772e60f71e0f
Reviewed-on: https://chromium-review.googlesource.com/c/1469544
Commit-Queue: Taiju Tsuiki <tzik@chromium.org>
Reviewed-by: 's avatarBenedikt Meurer <bmeurer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#59884}
parent 7cee0fad
...@@ -353,6 +353,8 @@ void Bootstrapper::DetachGlobal(Handle<Context> env) { ...@@ -353,6 +353,8 @@ void Bootstrapper::DetachGlobal(Handle<Context> env) {
if (FLAG_track_detached_contexts) { if (FLAG_track_detached_contexts) {
isolate_->AddDetachedContext(env); isolate_->AddDetachedContext(env);
} }
env->native_context()->set_microtask_queue(nullptr);
} }
namespace { namespace {
......
...@@ -35,7 +35,7 @@ class MicrotaskQueueBuiltinsAssembler : public CodeStubAssembler { ...@@ -35,7 +35,7 @@ class MicrotaskQueueBuiltinsAssembler : public CodeStubAssembler {
TNode<IntPtrT> start, TNode<IntPtrT> start,
TNode<IntPtrT> index); TNode<IntPtrT> index);
void PrepareForContext(TNode<Context> microtask_context); void PrepareForContext(TNode<Context> microtask_context, Label* bailout);
void RunSingleMicrotask(TNode<Context> current_context, void RunSingleMicrotask(TNode<Context> current_context,
TNode<Microtask> microtask); TNode<Microtask> microtask);
void IncrementFinishedMicrotaskCount(TNode<RawPtrT> microtask_queue); void IncrementFinishedMicrotaskCount(TNode<RawPtrT> microtask_queue);
...@@ -105,8 +105,13 @@ TNode<IntPtrT> MicrotaskQueueBuiltinsAssembler::CalculateRingBufferOffset( ...@@ -105,8 +105,13 @@ TNode<IntPtrT> MicrotaskQueueBuiltinsAssembler::CalculateRingBufferOffset(
} }
void MicrotaskQueueBuiltinsAssembler::PrepareForContext( void MicrotaskQueueBuiltinsAssembler::PrepareForContext(
TNode<Context> native_context) { TNode<Context> native_context, Label* bailout) {
CSA_ASSERT(this, IsNativeContext(native_context)); 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); EnterMicrotaskContext(native_context);
SetCurrentContext(native_context); SetCurrentContext(native_context);
} }
...@@ -151,7 +156,7 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask( ...@@ -151,7 +156,7 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask(
TNode<Context> microtask_context = TNode<Context> microtask_context =
LoadObjectField<Context>(microtask, CallableTask::kContextOffset); LoadObjectField<Context>(microtask, CallableTask::kContextOffset);
TNode<Context> native_context = LoadNativeContext(microtask_context); TNode<Context> native_context = LoadNativeContext(microtask_context);
PrepareForContext(native_context); PrepareForContext(native_context, &done);
TNode<JSReceiver> callable = TNode<JSReceiver> callable =
LoadObjectField<JSReceiver>(microtask, CallableTask::kCallableOffset); LoadObjectField<JSReceiver>(microtask, CallableTask::kCallableOffset);
...@@ -194,7 +199,7 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask( ...@@ -194,7 +199,7 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask(
TNode<Context> microtask_context = LoadObjectField<Context>( TNode<Context> microtask_context = LoadObjectField<Context>(
microtask, PromiseResolveThenableJobTask::kContextOffset); microtask, PromiseResolveThenableJobTask::kContextOffset);
TNode<Context> native_context = LoadNativeContext(microtask_context); TNode<Context> native_context = LoadNativeContext(microtask_context);
PrepareForContext(native_context); PrepareForContext(native_context, &done);
Node* const promise_to_resolve = LoadObjectField( Node* const promise_to_resolve = LoadObjectField(
microtask, PromiseResolveThenableJobTask::kPromiseToResolveOffset); microtask, PromiseResolveThenableJobTask::kPromiseToResolveOffset);
...@@ -218,7 +223,7 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask( ...@@ -218,7 +223,7 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask(
TNode<Context> microtask_context = LoadObjectField<Context>( TNode<Context> microtask_context = LoadObjectField<Context>(
microtask, PromiseReactionJobTask::kContextOffset); microtask, PromiseReactionJobTask::kContextOffset);
TNode<Context> native_context = LoadNativeContext(microtask_context); TNode<Context> native_context = LoadNativeContext(microtask_context);
PrepareForContext(native_context); PrepareForContext(native_context, &done);
Node* const argument = Node* const argument =
LoadObjectField(microtask, PromiseReactionJobTask::kArgumentOffset); LoadObjectField(microtask, PromiseReactionJobTask::kArgumentOffset);
...@@ -251,7 +256,7 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask( ...@@ -251,7 +256,7 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask(
TNode<Context> microtask_context = LoadObjectField<Context>( TNode<Context> microtask_context = LoadObjectField<Context>(
microtask, PromiseReactionJobTask::kContextOffset); microtask, PromiseReactionJobTask::kContextOffset);
TNode<Context> native_context = LoadNativeContext(microtask_context); TNode<Context> native_context = LoadNativeContext(microtask_context);
PrepareForContext(native_context); PrepareForContext(native_context, &done);
Node* const argument = Node* const argument =
LoadObjectField(microtask, PromiseReactionJobTask::kArgumentOffset); LoadObjectField(microtask, PromiseReactionJobTask::kArgumentOffset);
...@@ -287,7 +292,7 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask( ...@@ -287,7 +292,7 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask(
FinalizationGroupCleanupJobTask::kFinalizationGroupOffset); FinalizationGroupCleanupJobTask::kFinalizationGroupOffset);
TNode<Context> native_context = LoadObjectField<Context>( TNode<Context> native_context = LoadObjectField<Context>(
finalization_group, JSFinalizationGroup::kNativeContextOffset); finalization_group, JSFinalizationGroup::kNativeContextOffset);
PrepareForContext(native_context); PrepareForContext(native_context, &done);
Node* const result = CallRuntime(Runtime::kFinalizationGroupCleanupJob, Node* const result = CallRuntime(Runtime::kFinalizationGroupCleanupJob,
native_context, finalization_group); native_context, finalization_group);
...@@ -472,6 +477,11 @@ TF_BUILTIN(EnqueueMicrotask, MicrotaskQueueBuiltinsAssembler) { ...@@ -472,6 +477,11 @@ TF_BUILTIN(EnqueueMicrotask, MicrotaskQueueBuiltinsAssembler) {
TNode<Context> native_context = LoadNativeContext(context); TNode<Context> native_context = LoadNativeContext(context);
TNode<RawPtrT> microtask_queue = GetMicrotaskQueue(native_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<RawPtrT> ring_buffer = GetMicrotaskRingBuffer(microtask_queue);
TNode<IntPtrT> capacity = GetMicrotaskQueueCapacity(microtask_queue); TNode<IntPtrT> capacity = GetMicrotaskQueueCapacity(microtask_queue);
TNode<IntPtrT> size = GetMicrotaskQueueSize(microtask_queue); TNode<IntPtrT> size = GetMicrotaskQueueSize(microtask_queue);
...@@ -504,6 +514,9 @@ TF_BUILTIN(EnqueueMicrotask, MicrotaskQueueBuiltinsAssembler) { ...@@ -504,6 +514,9 @@ TF_BUILTIN(EnqueueMicrotask, MicrotaskQueueBuiltinsAssembler) {
isolate_constant, microtask_queue, microtask); isolate_constant, microtask_queue, microtask);
Return(UndefinedConstant()); Return(UndefinedConstant());
} }
Bind(&if_shutdown);
Return(UndefinedConstant());
} }
TF_BUILTIN(RunMicrotasks, MicrotaskQueueBuiltinsAssembler) { TF_BUILTIN(RunMicrotasks, MicrotaskQueueBuiltinsAssembler) {
......
...@@ -100,7 +100,8 @@ class JSReceiver : public HeapObject { ...@@ -100,7 +100,8 @@ class JSReceiver : public HeapObject {
bool use_set = true); bool use_set = true);
// Implementation of [[HasProperty]], ECMA-262 5th edition, section 8.12.6. // Implementation of [[HasProperty]], ECMA-262 5th edition, section 8.12.6.
V8_WARN_UNUSED_RESULT static Maybe<bool> HasProperty(LookupIterator* it); V8_EXPORT_PRIVATE V8_WARN_UNUSED_RESULT static Maybe<bool> HasProperty(
LookupIterator* it);
V8_WARN_UNUSED_RESULT static inline Maybe<bool> HasProperty( V8_WARN_UNUSED_RESULT static inline Maybe<bool> HasProperty(
Handle<JSReceiver> object, Handle<Name> name); Handle<JSReceiver> object, Handle<Name> name);
V8_WARN_UNUSED_RESULT static inline Maybe<bool> HasElement( V8_WARN_UNUSED_RESULT static inline Maybe<bool> HasElement(
......
...@@ -10,7 +10,10 @@ ...@@ -10,7 +10,10 @@
#include <vector> #include <vector>
#include "src/heap/factory.h" #include "src/heap/factory.h"
#include "src/objects-inl.h"
#include "src/objects/foreign.h" #include "src/objects/foreign.h"
#include "src/objects/js-array-inl.h"
#include "src/objects/js-objects-inl.h"
#include "src/objects/promise-inl.h" #include "src/objects/promise-inl.h"
#include "src/visitors.h" #include "src/visitors.h"
#include "test/unittests/test-utils.h" #include "test/unittests/test-utils.h"
...@@ -26,7 +29,29 @@ void RunStdFunction(void* data) { ...@@ -26,7 +29,29 @@ void RunStdFunction(void* data) {
(*f)(); (*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: public:
template <typename F> template <typename F>
Handle<Microtask> NewMicrotask(F&& f) { Handle<Microtask> NewMicrotask(F&& f) {
...@@ -56,6 +81,11 @@ class MicrotaskQueueTest : public TestWithNativeContext { ...@@ -56,6 +81,11 @@ class MicrotaskQueueTest : public TestWithNativeContext {
microtask_queue_ = nullptr; microtask_queue_ = nullptr;
} }
template <size_t N>
Handle<Name> NameFromChars(const char (&chars)[N]) {
return isolate()->factory()->NewStringFromStaticChars(chars);
}
private: private:
std::unique_ptr<MicrotaskQueue> microtask_queue_; std::unique_ptr<MicrotaskQueue> microtask_queue_;
}; };
...@@ -280,5 +310,193 @@ TEST_F(MicrotaskQueueTest, PromiseHandlerContext) { ...@@ -280,5 +310,193 @@ TEST_F(MicrotaskQueueTest, PromiseHandlerContext) {
v8_context2->DetachGlobal(); v8_context2->DetachGlobal();
} }
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());
}
TEST_F(MicrotaskQueueTest, DetachGlobal_HandlerContext) {
// EnqueueMicrotask should use the context associated to the handler instead
// of the current context. E.g.
// // At Context A.
// let resolved = Promise.resolve();
// // Call DetachGlobal on A, so that microtasks associated to A is
// // cancelled.
//
// // At Context B.
// let handler = () => {
// console.log("here");
// };
// // The microtask to run |handler| should be associated to B instead of A,
// // so that handler runs even |resolved| is on the detached context A.
// resolved.then(handler);
Handle<JSReceiver> results = isolate()->factory()->NewJSObjectWithNullProto();
// These belong to a stale Context.
Handle<JSPromise> stale_resolved_promise;
Handle<JSPromise> stale_rejected_promise;
Handle<JSReceiver> stale_handler;
Local<v8::Context> sub_context = v8::Context::New(v8_isolate());
{
v8::Context::Scope scope(sub_context);
stale_resolved_promise = RunJS<JSPromise>("Promise.resolve()");
stale_rejected_promise = RunJS<JSPromise>("Promise.reject()");
stale_handler = RunJS<JSReceiver>(
"(results, label) => {"
" results[label] = true;"
"}");
}
// DetachGlobal() cancells all microtasks associated to the context.
sub_context->DetachGlobal();
sub_context.Clear();
SetGlobalProperty("results", Utils::ToLocal(results));
SetGlobalProperty(
"stale_resolved_promise",
Utils::ToLocal(Handle<JSReceiver>::cast(stale_resolved_promise)));
SetGlobalProperty(
"stale_rejected_promise",
Utils::ToLocal(Handle<JSReceiver>::cast(stale_rejected_promise)));
SetGlobalProperty("stale_handler", Utils::ToLocal(stale_handler));
// Set valid handlers to stale promises.
RunJS(
"stale_resolved_promise.then(() => {"
" results['stale_resolved_promise'] = true;"
"})");
RunJS(
"stale_rejected_promise.catch(() => {"
" results['stale_rejected_promise'] = true;"
"})");
microtask_queue()->RunMicrotasks(isolate());
EXPECT_TRUE(
JSReceiver::HasProperty(results, NameFromChars("stale_resolved_promise"))
.FromJust());
EXPECT_TRUE(
JSReceiver::HasProperty(results, NameFromChars("stale_rejected_promise"))
.FromJust());
// Set stale handlers to valid promises.
RunJS(
"Promise.resolve("
" stale_handler.bind(null, results, 'stale_handler_resolve'))");
RunJS(
"Promise.reject("
" stale_handler.bind(null, results, 'stale_handler_reject'))");
microtask_queue()->RunMicrotasks(isolate());
EXPECT_FALSE(
JSReceiver::HasProperty(results, NameFromChars("stale_handler_resolve"))
.FromJust());
EXPECT_FALSE(
JSReceiver::HasProperty(results, NameFromChars("stale_handler_reject"))
.FromJust());
}
} // namespace internal } // namespace internal
} // namespace v8 } // 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