Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LibWeb: Implementation of HostEnqueueFinalizationRegistryCleanupJob called during GC allocates new GC objects #3051

Open
ADKaster opened this issue Dec 26, 2024 · 0 comments

Comments

@ADKaster
Copy link
Member

// 8.1.5.4.2 HostEnqueueFinalizationRegistryCleanupJob(finalizationRegistry), https://html.spec.whatwg.org/multipage/webappapis.html#hostenqueuefinalizationregistrycleanupjob
s_main_thread_vm->host_enqueue_finalization_registry_cleanup_job = [](JS::FinalizationRegistry& finalization_registry) {
// 1. Let global be finalizationRegistry.[[Realm]]'s global object.
auto& global = finalization_registry.realm().global_object();
// 2. Queue a global task on the JavaScript engine task source given global to perform the following steps:
HTML::queue_global_task(HTML::Task::Source::JavaScriptEngine, global, GC::create_function(s_main_thread_vm->heap(), [&finalization_registry] {
// 1. Let entry be finalizationRegistry.[[CleanupCallback]].[[Callback]].[[Realm]].
auto& entry = *finalization_registry.cleanup_callback().callback().realm();
// 2. Check if we can run script with entry. If this returns "do not run", then return.
if (HTML::can_run_script(entry) == HTML::RunScriptDecision::DoNotRun)
return;
// 3. Prepare to run script with entry.
HTML::prepare_to_run_script(entry);
// 4. Let result be the result of performing CleanupFinalizationRegistry(finalizationRegistry).
auto result = finalization_registry.cleanup();
// 5. Clean up after running script with entry.
HTML::clean_up_after_running_script(entry);
// 6. If result is an abrupt completion, then report the exception given by result.[[Value]].
if (result.is_error())
HTML::report_exception(result, entry);
}));
};

Here, we call GC::create_function to enqueue an HTML task to handle the finalization registry cleanup.

However, this VM hook is only ever called when scanning weak maps during a GC sweep.

As such, we can encounter crashes such as this, where the undefer_gc() from the allocation of the GC::Function will cause us to try to recursively collect garbage:

VERIFICATION FAILED: !m_collecting_garbage at /home/andrew/ladybird-org/ladybird-browser/Libraries/LibGC/Heap.cpp:242
/home/andrew/ladybird-org/ladybird-browser/Build/release/lib/liblagom-ak.so.0(ak_trap+0x4d) [0x76948baa3fad]
/home/andrew/ladybird-org/ladybird-browser/Build/release/lib/liblagom-ak.so.0(ak_verification_failed+0x43) [0x76948baa3f43]
/home/andrew/ladybird-org/ladybird-browser/Build/release/lib/liblagom-gc.so.0 GC::Heap::collect_garbage(GC::Heap::CollectionType, bool) 0x10c) [0x76948dbcc2ec]
/home/andrew/ladybird-org/ladybird-browser/Build/release/lib/liblagom-gc.so.0 GC::Heap::undefer_gc() 0x4e) [0x76948dbcc34e]
/home/andrew/ladybird-org/ladybird-browser/Build/release/lib/liblagom-web.so.0(+0x9448c9) [0x76948c5448c9]
/home/andrew/ladybird-org/ladybird-browser/Build/release/lib/liblagom-web.so.0(+0x98135f) [0x76948c58135f]
/home/andrew/ladybird-org/ladybird-browser/Build/release/lib/liblagom-js.so.0 JS::FinalizationRegistry::remove_dead_cells(AK::Badge<GC::Heap>) 0xad) [0x76948b76d8ad]
/home/andrew/ladybird-org/ladybird-browser/Build/release/lib/liblagom-gc.so.0 GC::Heap::sweep_dead_cells(bool, Core::ElapsedTimer const&) 0x1d1) [0x76948dbcbd41]
/home/andrew/ladybird-org/ladybird-browser/Build/release/lib/liblagom-gc.so.0 GC::Heap::collect_garbage(GC::Heap::CollectionType, bool) 0xcb) [0x76948dbcc2ab]
/home/andrew/ladybird-org/ladybird-browser/Build/release/lib/liblagom-gc.so.0 GC::Heap::undefer_gc() 0x4e) [0x76948dbcc34e]
/home/andrew/ladybird-org/ladybird-browser/Build/release/lib/liblagom-js.so.0 JS::ThrowCompletionOr<GC::Ref<JS::Object> > JS::ordinary_create_from_constructor<JS::Object, JS::Object::ConstructWithPrototypeTag>(JS::VM&, JS::FunctionObject const&, GC::Ref<JS::Object> (JS::Intrinsics::*)(), JS::Object::ConstructWithPrototypeTag&&) 0xa4) [0x76948b75aa34]
  • Allocate an object
  • heap.allocate() -> allocate() defer_gc(), placement new, undefer_gc()
  • do a GC sweep
    • ask finalization registry to do a sweep
    • host_enqueue_finalization_registry_cleanup_job
    • allocate a new GC::Function to do a deferred task
      • heap.allocate(), defer gc, placement new, undefer_gc
      • 💥 , undefer_gc while GC in progress, try to GC again

We should come up with a way to avoid allocations during a collection.

Or, with a way to make that not scary to do.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant