Commit 55a01ec7 authored by Shu-yu Guo's avatar Shu-yu Guo Committed by Commit Bot

Reland "[weakrefs] Schedule FinalizationGroup cleanup tasks from within V8"

Deprecate the following explicit FinalizationGroup APIs in favor of
automatic handling of FinalizationGroup cleanup callbacks:
  - v8::Isolate::SetHostCleanupFinalizationGroupCallback
  - v8::FinaliationGroup::Cleanup

If no HostCleanupFinalizationGroupCallback is set, then
FinalizationGroup cleanup callbacks are automatically scheduled by V8
itself as non-nestable foreground tasks.

When a Context being disposed, all FinalizationGroups that are
associated with it are removed from the dirty list, cancelling
scheduled cleanup.

This is a reland of 31d8ff7a

Bug: v8:8179, v8:10190
Change-Id: I704ecf48aeebac1dc2c05ea1c052f6a2560ae332
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2045723
Commit-Queue: Shu-yu Guo <syg@chromium.org>
Reviewed-by: 's avatarUlan Degenbaev <ulan@chromium.org>
Reviewed-by: 's avatarRoss McIlroy <rmcilroy@chromium.org>
Cr-Commit-Position: refs/heads/master@{#66208}
parent 841fd707
...@@ -2299,6 +2299,8 @@ v8_source_set("v8_base_without_compiler") { ...@@ -2299,6 +2299,8 @@ v8_source_set("v8_base_without_compiler") {
"src/heap/factory-inl.h", "src/heap/factory-inl.h",
"src/heap/factory.cc", "src/heap/factory.cc",
"src/heap/factory.h", "src/heap/factory.h",
"src/heap/finalization-group-cleanup-task.cc",
"src/heap/finalization-group-cleanup-task.h",
"src/heap/gc-idle-time-handler.cc", "src/heap/gc-idle-time-handler.cc",
"src/heap/gc-idle-time-handler.h", "src/heap/gc-idle-time-handler.h",
"src/heap/gc-tracer.cc", "src/heap/gc-tracer.cc",
......
...@@ -5912,6 +5912,9 @@ class V8_EXPORT FinalizationGroup : public Object { ...@@ -5912,6 +5912,9 @@ class V8_EXPORT FinalizationGroup : public Object {
* occurred. Otherwise the result is |true| if the cleanup callback * occurred. Otherwise the result is |true| if the cleanup callback
* was called successfully. The result is never |false|. * was called successfully. The result is never |false|.
*/ */
V8_DEPRECATED(
"FinalizationGroup cleanup is automatic if "
"HostCleanupFinalizationGroupCallback is not set")
static V8_WARN_UNUSED_RESULT Maybe<bool> Cleanup( static V8_WARN_UNUSED_RESULT Maybe<bool> Cleanup(
Local<FinalizationGroup> finalization_group); Local<FinalizationGroup> finalization_group);
}; };
...@@ -8481,6 +8484,9 @@ class V8_EXPORT Isolate { ...@@ -8481,6 +8484,9 @@ class V8_EXPORT Isolate {
* are ready to be cleaned up and require FinalizationGroup::Cleanup() * are ready to be cleaned up and require FinalizationGroup::Cleanup()
* to be called in a future task. * to be called in a future task.
*/ */
V8_DEPRECATED(
"FinalizationGroup cleanup is automatic if "
"HostCleanupFinalizationGroupCallback is not set")
void SetHostCleanupFinalizationGroupCallback( void SetHostCleanupFinalizationGroupCallback(
HostCleanupFinalizationGroupCallback callback); HostCleanupFinalizationGroupCallback callback);
...@@ -9085,10 +9091,10 @@ class V8_EXPORT Isolate { ...@@ -9085,10 +9091,10 @@ class V8_EXPORT Isolate {
void LowMemoryNotification(); void LowMemoryNotification();
/** /**
* Optional notification that a context has been disposed. V8 uses * Optional notification that a context has been disposed. V8 uses these
* these notifications to guide the GC heuristic. Returns the number * notifications to guide the GC heuristic and cancel FinalizationGroup
* of context disposals - including this one - since the last time * cleanup tasks. Returns the number of context disposals - including this one
* V8 had a chance to clean up. * - since the last time V8 had a chance to clean up.
* *
* The optional parameter |dependant_context| specifies whether the disposed * The optional parameter |dependant_context| specifies whether the disposed
* context was depending on state from other contexts or not. * context was depending on state from other contexts or not.
......
...@@ -11033,6 +11033,26 @@ void InvokeFunctionCallback(const v8::FunctionCallbackInfo<v8::Value>& info, ...@@ -11033,6 +11033,26 @@ void InvokeFunctionCallback(const v8::FunctionCallbackInfo<v8::Value>& info,
callback(info); callback(info);
} }
void InvokeFinalizationGroupCleanupFromTask(
Handle<Context> context, Handle<JSFinalizationGroup> finalization_group,
Handle<Object> callback) {
Isolate* isolate = finalization_group->native_context().GetIsolate();
RuntimeCallTimerScope timer(
isolate, RuntimeCallCounterId::kFinalizationGroupCleanupFromTask);
// Do not use ENTER_V8 because this is always called from a running
// FinalizationGroupCleanupTask within V8 and we should not log it as an API
// call. This method is implemented here to avoid duplication of the exception
// handling and microtask running logic in CallDepthScope.
if (IsExecutionTerminatingCheck(isolate)) return;
Local<v8::Context> api_context = Utils::ToLocal(context);
CallDepthScope<true> call_depth_scope(isolate, api_context);
VMState<OTHER> state(isolate);
if (JSFinalizationGroup::Cleanup(isolate, finalization_group, callback)
.IsNothing()) {
call_depth_scope.Escape();
}
}
// Undefine macros for jumbo build. // Undefine macros for jumbo build.
#undef LOG_API #undef LOG_API
#undef ENTER_V8_DO_NOT_USE #undef ENTER_V8_DO_NOT_USE
......
...@@ -26,6 +26,7 @@ namespace v8 { ...@@ -26,6 +26,7 @@ namespace v8 {
namespace internal { namespace internal {
class JSArrayBufferView; class JSArrayBufferView;
class JSFinalizationGroup;
} // namespace internal } // namespace internal
namespace debug { namespace debug {
...@@ -561,6 +562,10 @@ void InvokeAccessorGetterCallback( ...@@ -561,6 +562,10 @@ void InvokeAccessorGetterCallback(
void InvokeFunctionCallback(const v8::FunctionCallbackInfo<v8::Value>& info, void InvokeFunctionCallback(const v8::FunctionCallbackInfo<v8::Value>& info,
v8::FunctionCallback callback); v8::FunctionCallback callback);
void InvokeFinalizationGroupCleanupFromTask(
Handle<Context> context, Handle<JSFinalizationGroup> finalization_group,
Handle<Object> callback);
} // namespace internal } // namespace internal
} // namespace v8 } // namespace v8
......
...@@ -148,9 +148,8 @@ BUILTIN(FinalizationGroupCleanupSome) { ...@@ -148,9 +148,8 @@ BUILTIN(FinalizationGroupCleanupSome) {
callback = callback_obj; callback = callback_obj;
} }
// Don't do set_scheduled_for_cleanup(false); we still have the microtask // Don't do set_scheduled_for_cleanup(false); we still have the task
// scheduled and don't want to schedule another one in case the user never // scheduled.
// executes microtasks.
if (JSFinalizationGroup::Cleanup(isolate, finalization_group, callback) if (JSFinalizationGroup::Cleanup(isolate, finalization_group, callback)
.IsNothing()) { .IsNothing()) {
DCHECK(isolate->has_pending_exception()); DCHECK(isolate->has_pending_exception());
......
...@@ -916,16 +916,6 @@ MaybeLocal<Promise> Shell::HostImportModuleDynamically( ...@@ -916,16 +916,6 @@ MaybeLocal<Promise> Shell::HostImportModuleDynamically(
return MaybeLocal<Promise>(); return MaybeLocal<Promise>();
} }
void Shell::HostCleanupFinalizationGroup(Local<Context> context,
Local<FinalizationGroup> fg) {
Isolate* isolate = context->GetIsolate();
PerIsolateData::Get(isolate)->HostCleanupFinalizationGroup(fg);
}
void PerIsolateData::HostCleanupFinalizationGroup(Local<FinalizationGroup> fg) {
cleanup_finalization_groups_.emplace(isolate_, fg);
}
void Shell::HostInitializeImportMetaObject(Local<Context> context, void Shell::HostInitializeImportMetaObject(Local<Context> context,
Local<Module> module, Local<Module> module,
Local<Object> meta) { Local<Object> meta) {
...@@ -1123,15 +1113,6 @@ MaybeLocal<Context> PerIsolateData::GetTimeoutContext() { ...@@ -1123,15 +1113,6 @@ MaybeLocal<Context> PerIsolateData::GetTimeoutContext() {
return result; return result;
} }
MaybeLocal<FinalizationGroup> PerIsolateData::GetCleanupFinalizationGroup() {
if (cleanup_finalization_groups_.empty())
return MaybeLocal<FinalizationGroup>();
Local<FinalizationGroup> result =
cleanup_finalization_groups_.front().Get(isolate_);
cleanup_finalization_groups_.pop();
return result;
}
PerIsolateData::RealmScope::RealmScope(PerIsolateData* data) : data_(data) { PerIsolateData::RealmScope::RealmScope(PerIsolateData* data) : data_(data) {
data_->realm_count_ = 1; data_->realm_count_ = 1;
data_->realm_current_ = 0; data_->realm_current_ = 0;
...@@ -1281,8 +1262,11 @@ void Shell::DisposeRealm(const v8::FunctionCallbackInfo<v8::Value>& args, ...@@ -1281,8 +1262,11 @@ void Shell::DisposeRealm(const v8::FunctionCallbackInfo<v8::Value>& args,
int index) { int index) {
Isolate* isolate = args.GetIsolate(); Isolate* isolate = args.GetIsolate();
PerIsolateData* data = PerIsolateData::Get(isolate); PerIsolateData* data = PerIsolateData::Get(isolate);
DisposeModuleEmbedderData(data->realms_[index].Get(isolate)); Local<Context> context = data->realms_[index].Get(isolate);
DisposeModuleEmbedderData(context);
data->realms_[index].Reset(); data->realms_[index].Reset();
// ContextDisposedNotification expects the disposed context to be entered.
v8::Context::Scope scope(context);
isolate->ContextDisposedNotification(); isolate->ContextDisposedNotification();
isolate->IdleNotificationDeadline(g_platform->MonotonicallyIncreasingTime()); isolate->IdleNotificationDeadline(g_platform->MonotonicallyIncreasingTime());
} }
...@@ -2742,8 +2726,6 @@ void SourceGroup::ExecuteInThread() { ...@@ -2742,8 +2726,6 @@ void SourceGroup::ExecuteInThread() {
Isolate::CreateParams create_params; Isolate::CreateParams create_params;
create_params.array_buffer_allocator = Shell::array_buffer_allocator; create_params.array_buffer_allocator = Shell::array_buffer_allocator;
Isolate* isolate = Isolate::New(create_params); Isolate* isolate = Isolate::New(create_params);
isolate->SetHostCleanupFinalizationGroupCallback(
Shell::HostCleanupFinalizationGroup);
isolate->SetHostImportModuleDynamicallyCallback( isolate->SetHostImportModuleDynamicallyCallback(
Shell::HostImportModuleDynamically); Shell::HostImportModuleDynamically);
isolate->SetHostInitializeImportMetaObjectCallback( isolate->SetHostInitializeImportMetaObjectCallback(
...@@ -2889,8 +2871,6 @@ void Worker::ExecuteInThread() { ...@@ -2889,8 +2871,6 @@ void Worker::ExecuteInThread() {
Isolate::CreateParams create_params; Isolate::CreateParams create_params;
create_params.array_buffer_allocator = Shell::array_buffer_allocator; create_params.array_buffer_allocator = Shell::array_buffer_allocator;
Isolate* isolate = Isolate::New(create_params); Isolate* isolate = Isolate::New(create_params);
isolate->SetHostCleanupFinalizationGroupCallback(
Shell::HostCleanupFinalizationGroup);
isolate->SetHostImportModuleDynamicallyCallback( isolate->SetHostImportModuleDynamicallyCallback(
Shell::HostImportModuleDynamically); Shell::HostImportModuleDynamically);
isolate->SetHostInitializeImportMetaObjectCallback( isolate->SetHostInitializeImportMetaObjectCallback(
...@@ -3272,21 +3252,6 @@ bool RunSetTimeoutCallback(Isolate* isolate, bool* did_run) { ...@@ -3272,21 +3252,6 @@ bool RunSetTimeoutCallback(Isolate* isolate, bool* did_run) {
return true; return true;
} }
bool RunCleanupFinalizationGroupCallback(Isolate* isolate, bool* did_run) {
PerIsolateData* data = PerIsolateData::Get(isolate);
HandleScope handle_scope(isolate);
while (true) {
Local<FinalizationGroup> fg;
if (!data->GetCleanupFinalizationGroup().ToLocal(&fg)) return true;
*did_run = true;
TryCatch try_catch(isolate);
try_catch.SetVerbose(true);
if (FinalizationGroup::Cleanup(fg).IsNothing()) {
return false;
}
}
}
bool ProcessMessages( bool ProcessMessages(
Isolate* isolate, Isolate* isolate,
const std::function<platform::MessageLoopBehavior()>& behavior) { const std::function<platform::MessageLoopBehavior()>& behavior) {
...@@ -3303,17 +3268,12 @@ bool ProcessMessages( ...@@ -3303,17 +3268,12 @@ bool ProcessMessages(
v8::platform::RunIdleTasks(g_default_platform, isolate, v8::platform::RunIdleTasks(g_default_platform, isolate,
50.0 / base::Time::kMillisecondsPerSecond); 50.0 / base::Time::kMillisecondsPerSecond);
} }
bool ran_finalization_callback = false;
if (!RunCleanupFinalizationGroupCallback(isolate,
&ran_finalization_callback)) {
return false;
}
bool ran_set_timeout = false; bool ran_set_timeout = false;
if (!RunSetTimeoutCallback(isolate, &ran_set_timeout)) { if (!RunSetTimeoutCallback(isolate, &ran_set_timeout)) {
return false; return false;
} }
if (!ran_set_timeout && !ran_finalization_callback) return true; if (!ran_set_timeout) return true;
} }
return true; return true;
} }
...@@ -3768,8 +3728,6 @@ int Shell::Main(int argc, char* argv[]) { ...@@ -3768,8 +3728,6 @@ int Shell::Main(int argc, char* argv[]) {
} }
Isolate* isolate = Isolate::New(create_params); Isolate* isolate = Isolate::New(create_params);
isolate->SetHostCleanupFinalizationGroupCallback(
Shell::HostCleanupFinalizationGroup);
isolate->SetHostImportModuleDynamicallyCallback( isolate->SetHostImportModuleDynamicallyCallback(
Shell::HostImportModuleDynamically); Shell::HostImportModuleDynamically);
isolate->SetHostInitializeImportMetaObjectCallback( isolate->SetHostInitializeImportMetaObjectCallback(
...@@ -3832,8 +3790,6 @@ int Shell::Main(int argc, char* argv[]) { ...@@ -3832,8 +3790,6 @@ int Shell::Main(int argc, char* argv[]) {
i::FLAG_hash_seed ^= 1337; // Use a different hash seed. i::FLAG_hash_seed ^= 1337; // Use a different hash seed.
Isolate* isolate2 = Isolate::New(create_params); Isolate* isolate2 = Isolate::New(create_params);
i::FLAG_hash_seed ^= 1337; // Restore old hash seed. i::FLAG_hash_seed ^= 1337; // Restore old hash seed.
isolate2->SetHostCleanupFinalizationGroupCallback(
Shell::HostCleanupFinalizationGroup);
isolate2->SetHostImportModuleDynamicallyCallback( isolate2->SetHostImportModuleDynamicallyCallback(
Shell::HostImportModuleDynamically); Shell::HostImportModuleDynamically);
isolate2->SetHostInitializeImportMetaObjectCallback( isolate2->SetHostInitializeImportMetaObjectCallback(
......
...@@ -226,8 +226,6 @@ class PerIsolateData { ...@@ -226,8 +226,6 @@ class PerIsolateData {
PerIsolateData* data_; PerIsolateData* data_;
}; };
inline void HostCleanupFinalizationGroup(Local<FinalizationGroup> fg);
inline MaybeLocal<FinalizationGroup> GetCleanupFinalizationGroup();
inline void SetTimeout(Local<Function> callback, Local<Context> context); inline void SetTimeout(Local<Function> callback, Local<Context> context);
inline MaybeLocal<Function> GetTimeoutCallback(); inline MaybeLocal<Function> GetTimeoutCallback();
inline MaybeLocal<Context> GetTimeoutContext(); inline MaybeLocal<Context> GetTimeoutContext();
...@@ -245,7 +243,6 @@ class PerIsolateData { ...@@ -245,7 +243,6 @@ class PerIsolateData {
Global<Value> realm_shared_; Global<Value> realm_shared_;
std::queue<Global<Function>> set_timeout_callbacks_; std::queue<Global<Function>> set_timeout_callbacks_;
std::queue<Global<Context>> set_timeout_contexts_; std::queue<Global<Context>> set_timeout_contexts_;
std::queue<Global<FinalizationGroup>> cleanup_finalization_groups_;
AsyncHooks* async_hooks_wrapper_; AsyncHooks* async_hooks_wrapper_;
int RealmIndexOrThrow(const v8::FunctionCallbackInfo<v8::Value>& args, int RealmIndexOrThrow(const v8::FunctionCallbackInfo<v8::Value>& args,
...@@ -423,8 +420,6 @@ class Shell : public i::AllStatic { ...@@ -423,8 +420,6 @@ class Shell : public i::AllStatic {
static void SetUMask(const v8::FunctionCallbackInfo<v8::Value>& args); static void SetUMask(const v8::FunctionCallbackInfo<v8::Value>& args);
static void MakeDirectory(const v8::FunctionCallbackInfo<v8::Value>& args); static void MakeDirectory(const v8::FunctionCallbackInfo<v8::Value>& args);
static void RemoveDirectory(const v8::FunctionCallbackInfo<v8::Value>& args); static void RemoveDirectory(const v8::FunctionCallbackInfo<v8::Value>& args);
static void HostCleanupFinalizationGroup(Local<Context> context,
Local<FinalizationGroup> fg);
static MaybeLocal<Promise> HostImportModuleDynamically( static MaybeLocal<Promise> HostImportModuleDynamically(
Local<Context> context, Local<ScriptOrModule> referrer, Local<Context> context, Local<ScriptOrModule> referrer,
Local<String> specifier); Local<String> specifier);
......
...@@ -1404,6 +1404,10 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory { ...@@ -1404,6 +1404,10 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory {
void ClearKeptObjects(); void ClearKeptObjects();
void SetHostCleanupFinalizationGroupCallback( void SetHostCleanupFinalizationGroupCallback(
HostCleanupFinalizationGroupCallback callback); HostCleanupFinalizationGroupCallback callback);
HostCleanupFinalizationGroupCallback
host_cleanup_finalization_group_callback() const {
return host_cleanup_finalization_group_callback_;
}
void RunHostCleanupFinalizationGroupCallback(Handle<JSFinalizationGroup> fg); void RunHostCleanupFinalizationGroupCallback(Handle<JSFinalizationGroup> fg);
void SetHostImportModuleDynamicallyCallback( void SetHostImportModuleDynamicallyCallback(
......
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/heap/finalization-group-cleanup-task.h"
#include "src/execution/frames.h"
#include "src/execution/interrupts-scope.h"
#include "src/execution/stack-guard.h"
#include "src/execution/v8threads.h"
#include "src/heap/heap-inl.h"
#include "src/objects/js-weak-refs-inl.h"
#include "src/tracing/trace-event.h"
namespace v8 {
namespace internal {
FinalizationGroupCleanupTask::FinalizationGroupCleanupTask(Heap* heap)
: heap_(heap) {}
void FinalizationGroupCleanupTask::SlowAssertNoActiveJavaScript() {
#ifdef ENABLE_SLOW_DCHECKS
class NoActiveJavaScript : public ThreadVisitor {
public:
void VisitThread(Isolate* isolate, ThreadLocalTop* top) override {
for (StackFrameIterator it(isolate, top); !it.done(); it.Advance()) {
DCHECK(!it.frame()->is_java_script());
}
}
};
NoActiveJavaScript no_active_js_visitor;
Isolate* isolate = heap_->isolate();
no_active_js_visitor.VisitThread(isolate, isolate->thread_local_top());
isolate->thread_manager()->IterateArchivedThreads(&no_active_js_visitor);
#endif // ENABLE_SLOW_DCHECKS
}
void FinalizationGroupCleanupTask::Run() {
Isolate* isolate = heap_->isolate();
DCHECK(!isolate->host_cleanup_finalization_group_callback());
SlowAssertNoActiveJavaScript();
TRACE_EVENT_CALL_STATS_SCOPED(isolate, "v8",
"V8.FinalizationGroupCleanupTask");
HandleScope handle_scope(isolate);
Handle<JSFinalizationGroup> finalization_group;
// There could be no dirty FinalizationGroups. When a context is disposed by
// the embedder, its FinalizationGroups are removed from the dirty list.
if (!heap_->TakeOneDirtyJSFinalizationGroup().ToHandle(&finalization_group)) {
return;
}
finalization_group->set_scheduled_for_cleanup(false);
// Since FinalizationGroup cleanup callbacks are scheduled by V8, enter the
// FinalizationGroup's context.
Handle<Context> context(Context::cast(finalization_group->native_context()),
isolate);
Handle<Object> callback(finalization_group->cleanup(), isolate);
v8::Context::Scope context_scope(v8::Utils::ToLocal(context));
v8::TryCatch catcher(reinterpret_cast<v8::Isolate*>(isolate));
catcher.SetVerbose(true);
// Exceptions are reported via the message handler. This is ensured by the
// verbose TryCatch.
InvokeFinalizationGroupCleanupFromTask(context, finalization_group, callback);
// Repost if there are remaining dirty FinalizationGroups.
heap_->set_is_finalization_group_cleanup_task_posted(false);
heap_->PostFinalizationGroupCleanupTaskIfNeeded();
}
} // namespace internal
} // namespace v8
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef V8_HEAP_FINALIZATION_GROUP_CLEANUP_TASK_H_
#define V8_HEAP_FINALIZATION_GROUP_CLEANUP_TASK_H_
#include "include/v8-platform.h"
#include "src/objects/js-weak-refs.h"
namespace v8 {
namespace internal {
// The GC schedules a cleanup task when the dirty FinalizationGroup list is
// non-empty. The task processes a single FinalizationGroup and posts another
// cleanup task if there are remaining dirty FinalizationGroups on the list.
class FinalizationGroupCleanupTask : public Task {
public:
explicit FinalizationGroupCleanupTask(Heap* heap);
~FinalizationGroupCleanupTask() override = default;
void Run() override;
private:
FinalizationGroupCleanupTask(const FinalizationGroupCleanupTask&) = delete;
void operator=(const FinalizationGroupCleanupTask&) = delete;
void SlowAssertNoActiveJavaScript();
Heap* heap_;
};
} // namespace internal
} // namespace v8
#endif // V8_HEAP_FINALIZATION_GROUP_CLEANUP_TASK_H_
...@@ -640,6 +640,10 @@ void Heap::DecrementExternalBackingStoreBytes(ExternalBackingStoreType type, ...@@ -640,6 +640,10 @@ void Heap::DecrementExternalBackingStoreBytes(ExternalBackingStoreType type,
base::CheckedDecrement(&backing_store_bytes_, amount); base::CheckedDecrement(&backing_store_bytes_, amount);
} }
bool Heap::HasDirtyJSFinalizationGroups() {
return !dirty_js_finalization_groups().IsUndefined(isolate());
}
AlwaysAllocateScope::AlwaysAllocateScope(Heap* heap) : heap_(heap) { AlwaysAllocateScope::AlwaysAllocateScope(Heap* heap) : heap_(heap) {
heap_->always_allocate_scope_count_++; heap_->always_allocate_scope_count_++;
} }
......
...@@ -33,6 +33,7 @@ ...@@ -33,6 +33,7 @@
#include "src/heap/combined-heap.h" #include "src/heap/combined-heap.h"
#include "src/heap/concurrent-marking.h" #include "src/heap/concurrent-marking.h"
#include "src/heap/embedder-tracing.h" #include "src/heap/embedder-tracing.h"
#include "src/heap/finalization-group-cleanup-task.h"
#include "src/heap/gc-idle-time-handler.h" #include "src/heap/gc-idle-time-handler.h"
#include "src/heap/gc-tracer.h" #include "src/heap/gc-tracer.h"
#include "src/heap/heap-controller.h" #include "src/heap/heap-controller.h"
...@@ -1197,17 +1198,11 @@ void Heap::GarbageCollectionEpilogue() { ...@@ -1197,17 +1198,11 @@ void Heap::GarbageCollectionEpilogue() {
ReduceNewSpaceSize(); ReduceNewSpaceSize();
} }
if (FLAG_harmony_weak_refs) { if (FLAG_harmony_weak_refs &&
isolate()->host_cleanup_finalization_group_callback()) {
HandleScope handle_scope(isolate()); HandleScope handle_scope(isolate());
while (!isolate()->heap()->dirty_js_finalization_groups().IsUndefined( Handle<JSFinalizationGroup> finalization_group;
isolate())) { while (TakeOneDirtyJSFinalizationGroup().ToHandle(&finalization_group)) {
Handle<JSFinalizationGroup> finalization_group(
JSFinalizationGroup::cast(
isolate()->heap()->dirty_js_finalization_groups()),
isolate());
isolate()->heap()->set_dirty_js_finalization_groups(
finalization_group->next());
finalization_group->set_next(ReadOnlyRoots(isolate()).undefined_value());
isolate()->RunHostCleanupFinalizationGroupCallback(finalization_group); isolate()->RunHostCleanupFinalizationGroupCallback(finalization_group);
} }
} }
...@@ -1662,6 +1657,9 @@ int Heap::NotifyContextDisposed(bool dependant_context) { ...@@ -1662,6 +1657,9 @@ int Heap::NotifyContextDisposed(bool dependant_context) {
memory_reducer_->NotifyPossibleGarbage(event); memory_reducer_->NotifyPossibleGarbage(event);
} }
isolate()->AbortConcurrentOptimization(BlockingBehavior::kDontBlock); isolate()->AbortConcurrentOptimization(BlockingBehavior::kDontBlock);
if (!isolate()->context().is_null()) {
RemoveDirtyFinalizationGroupsOnContext(isolate()->raw_native_context());
}
number_of_disposed_maps_ = retained_maps().length(); number_of_disposed_maps_ = retained_maps().length();
tracer()->AddContextDisposalTime(MonotonicallyIncreasingTimeInMs()); tracer()->AddContextDisposalTime(MonotonicallyIncreasingTimeInMs());
...@@ -6043,11 +6041,25 @@ void Heap::SetInterpreterEntryTrampolineForProfiling(Code code) { ...@@ -6043,11 +6041,25 @@ void Heap::SetInterpreterEntryTrampolineForProfiling(Code code) {
set_interpreter_entry_trampoline_for_profiling(code); set_interpreter_entry_trampoline_for_profiling(code);
} }
void Heap::PostFinalizationGroupCleanupTaskIfNeeded() {
DCHECK(!isolate()->host_cleanup_finalization_group_callback());
// Only one cleanup task is posted at a time.
if (!HasDirtyJSFinalizationGroups() ||
is_finalization_group_cleanup_task_posted_) {
return;
}
auto taskrunner = V8::GetCurrentPlatform()->GetForegroundTaskRunner(
reinterpret_cast<v8::Isolate*>(isolate()));
auto task = std::make_unique<FinalizationGroupCleanupTask>(this);
taskrunner->PostNonNestableTask(std::move(task));
is_finalization_group_cleanup_task_posted_ = true;
}
void Heap::AddDirtyJSFinalizationGroup( void Heap::AddDirtyJSFinalizationGroup(
JSFinalizationGroup finalization_group, JSFinalizationGroup finalization_group,
std::function<void(HeapObject object, ObjectSlot slot, Object target)> std::function<void(HeapObject object, ObjectSlot slot, Object target)>
gc_notify_updated_slot) { gc_notify_updated_slot) {
DCHECK(dirty_js_finalization_groups().IsUndefined(isolate()) || DCHECK(!HasDirtyJSFinalizationGroups() ||
dirty_js_finalization_groups().IsJSFinalizationGroup()); dirty_js_finalization_groups().IsJSFinalizationGroup());
DCHECK(finalization_group.next().IsUndefined(isolate())); DCHECK(finalization_group.next().IsUndefined(isolate()));
DCHECK(!finalization_group.scheduled_for_cleanup()); DCHECK(!finalization_group.scheduled_for_cleanup());
...@@ -6062,6 +6074,44 @@ void Heap::AddDirtyJSFinalizationGroup( ...@@ -6062,6 +6074,44 @@ void Heap::AddDirtyJSFinalizationGroup(
// for the root pointing to the first JSFinalizationGroup. // for the root pointing to the first JSFinalizationGroup.
} }
MaybeHandle<JSFinalizationGroup> Heap::TakeOneDirtyJSFinalizationGroup() {
if (HasDirtyJSFinalizationGroups()) {
Handle<JSFinalizationGroup> finalization_group(
JSFinalizationGroup::cast(dirty_js_finalization_groups()), isolate());
set_dirty_js_finalization_groups(finalization_group->next());
finalization_group->set_next(ReadOnlyRoots(isolate()).undefined_value());
return finalization_group;
}
return {};
}
void Heap::RemoveDirtyFinalizationGroupsOnContext(NativeContext context) {
if (!FLAG_harmony_weak_refs) return;
if (isolate()->host_cleanup_finalization_group_callback()) return;
DisallowHeapAllocation no_gc;
Isolate* isolate = this->isolate();
Object prev = ReadOnlyRoots(isolate).undefined_value();
Object current = dirty_js_finalization_groups();
while (!current.IsUndefined(isolate)) {
JSFinalizationGroup finalization_group = JSFinalizationGroup::cast(current);
if (finalization_group.native_context() == context) {
if (prev.IsUndefined(isolate)) {
set_dirty_js_finalization_groups(finalization_group.next());
} else {
JSFinalizationGroup::cast(prev).set_next(finalization_group.next());
}
finalization_group.set_scheduled_for_cleanup(false);
current = finalization_group.next();
finalization_group.set_next(ReadOnlyRoots(isolate).undefined_value());
} else {
prev = current;
current = finalization_group.next();
}
}
}
void Heap::KeepDuringJob(Handle<JSReceiver> target) { void Heap::KeepDuringJob(Handle<JSReceiver> target) {
DCHECK(FLAG_harmony_weak_refs); DCHECK(FLAG_harmony_weak_refs);
DCHECK(weak_refs_keep_during_job().IsUndefined() || DCHECK(weak_refs_keep_during_job().IsUndefined() ||
......
...@@ -800,12 +800,33 @@ class Heap { ...@@ -800,12 +800,33 @@ class Heap {
// See also: FLAG_interpreted_frames_native_stack. // See also: FLAG_interpreted_frames_native_stack.
void SetInterpreterEntryTrampolineForProfiling(Code code); void SetInterpreterEntryTrampolineForProfiling(Code code);
// Add finalization_group into the dirty_js_finalization_groups list. // Add finalization_group to the end of the dirty_js_finalization_groups list.
void AddDirtyJSFinalizationGroup( void AddDirtyJSFinalizationGroup(
JSFinalizationGroup finalization_group, JSFinalizationGroup finalization_group,
std::function<void(HeapObject object, ObjectSlot slot, Object target)> std::function<void(HeapObject object, ObjectSlot slot, Object target)>
gc_notify_updated_slot); gc_notify_updated_slot);
// Pop and return the head of the dirty_js_finalization_groups list.
MaybeHandle<JSFinalizationGroup> TakeOneDirtyJSFinalizationGroup();
// Called from Heap::NotifyContextDisposed to remove all FinalizationGroups
// with {context} from the dirty list when the context e.g. navigates away or
// is detached. If the dirty list is empty afterwards, the cleanup task is
// aborted if needed.
void RemoveDirtyFinalizationGroupsOnContext(NativeContext context);
inline bool HasDirtyJSFinalizationGroups();
void PostFinalizationGroupCleanupTaskIfNeeded();
void set_is_finalization_group_cleanup_task_posted(bool posted) {
is_finalization_group_cleanup_task_posted_ = posted;
}
bool is_finalization_group_cleanup_task_posted() {
return is_finalization_group_cleanup_task_posted_;
}
V8_EXPORT_PRIVATE void KeepDuringJob(Handle<JSReceiver> target); V8_EXPORT_PRIVATE void KeepDuringJob(Handle<JSReceiver> target);
void ClearKeptObjects(); void ClearKeptObjects();
...@@ -2156,6 +2177,8 @@ class Heap { ...@@ -2156,6 +2177,8 @@ class Heap {
std::vector<HeapObjectAllocationTracker*> allocation_trackers_; std::vector<HeapObjectAllocationTracker*> allocation_trackers_;
bool is_finalization_group_cleanup_task_posted_ = false;
std::unique_ptr<third_party_heap::Heap> tp_heap_; std::unique_ptr<third_party_heap::Heap> tp_heap_;
// Classes in "heap" can be friends. // Classes in "heap" can be friends.
......
...@@ -2534,6 +2534,9 @@ void MarkCompactCollector::ClearJSWeakRefs() { ...@@ -2534,6 +2534,9 @@ void MarkCompactCollector::ClearJSWeakRefs() {
RecordSlot(weak_cell, slot, HeapObject::cast(*slot)); RecordSlot(weak_cell, slot, HeapObject::cast(*slot));
} }
} }
if (!isolate()->host_cleanup_finalization_group_callback()) {
heap()->PostFinalizationGroupCleanupTaskIfNeeded();
}
} }
void MarkCompactCollector::AbortWeakObjects() { void MarkCompactCollector::AbortWeakObjects() {
......
...@@ -989,6 +989,7 @@ class RuntimeCallTimer final { ...@@ -989,6 +989,7 @@ class RuntimeCallTimer final {
V(DeoptimizeCode) \ V(DeoptimizeCode) \
V(DeserializeContext) \ V(DeserializeContext) \
V(DeserializeIsolate) \ V(DeserializeIsolate) \
V(FinalizationGroupCleanupFromTask) \
V(FunctionCallback) \ V(FunctionCallback) \
V(FunctionLengthGetter) \ V(FunctionLengthGetter) \
V(FunctionPrototypeGetter) \ V(FunctionPrototypeGetter) \
......
...@@ -6,7 +6,6 @@ ...@@ -6,7 +6,6 @@
#define V8_OBJECTS_JS_WEAK_REFS_H_ #define V8_OBJECTS_JS_WEAK_REFS_H_
#include "src/objects/js-objects.h" #include "src/objects/js-objects.h"
#include "src/objects/microtask.h"
// Has to be the last include (doesn't have include guards): // Has to be the last include (doesn't have include guards):
#include "src/objects/object-macros.h" #include "src/objects/object-macros.h"
......
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --harmony-weak-refs --expose-gc --noincremental-marking
// Flags: --no-stress-opt
// Since cleanup tasks are top-level tasks, errors thrown from them don't stop
// future cleanup tasks from running.
function callback(iter) {
[...iter];
throw new Error('callback');
};
const fg1 = new FinalizationGroup(callback);
const fg2 = new FinalizationGroup(callback);
(function() {
let x = {};
fg1.register(x, {});
fg2.register(x, {});
x = null;
})();
gc();
*%(basename)s:{NUMBER}: Error: callback
throw new Error('callback');
^
Error: callback
at callback (*%(basename)s:{NUMBER}:{NUMBER})
*%(basename)s:{NUMBER}: Error: callback
throw new Error('callback');
^
Error: callback
at callback (*%(basename)s:{NUMBER}:{NUMBER})
...@@ -9,15 +9,28 @@ let r = Realm.create(); ...@@ -9,15 +9,28 @@ let r = Realm.create();
let FG = Realm.eval(r, "FinalizationGroup"); let FG = Realm.eval(r, "FinalizationGroup");
Realm.detachGlobal(r); Realm.detachGlobal(r);
let fg_not_run = new FG(() => {
assertUnreachable();
});
(() => {
fg_not_run.register({});
})();
gc();
// Disposing the realm cancels the already scheduled fg_not_run's finalizer.
Realm.dispose(r);
let fg = new FG(()=> { let fg = new FG(()=> {
cleanedUp = true; cleanedUp = true;
}); });
// FGs that are alive after disposal can still schedule tasks.
(() => { (() => {
let object = {}; let object = {};
fg.register(object, {}); fg.register(object, {});
// object goes out of scope. // object becomes unreachable.
})(); })();
gc(); gc();
......
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --harmony-weak-refs --expose-gc --noincremental-marking
let call_count = 0;
let reentrant_gc =
function(iter) {
[...iter];
gc();
call_count++;
}
let fg = new FinalizationGroup(reentrant_gc);
(function() {
fg.register({}, 42);
})();
gc();
setTimeout(function() {
assertEquals(1, call_count);
}, 0);
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