LibJS: Stop having AsyncFunctionDriverWrapper leak itself
Some checks are pending
CI / Lagom (false, FUZZ, ubuntu-24.04, Linux, Clang) (push) Waiting to run
CI / Lagom (false, NO_FUZZ, macos-15, macOS, Clang) (push) Waiting to run
CI / Lagom (false, NO_FUZZ, ubuntu-24.04, Linux, GNU) (push) Waiting to run
CI / Lagom (true, NO_FUZZ, ubuntu-24.04, Linux, Clang) (push) Waiting to run
Package the js repl as a binary artifact / build-and-package (macos-14, macOS, macOS-universal2) (push) Waiting to run
Package the js repl as a binary artifact / build-and-package (ubuntu-24.04, Linux, Linux-x86_64) (push) Waiting to run
Run test262 and test-wasm / run_and_update_results (push) Waiting to run
Lint Code / lint (push) Waiting to run
Push notes / build (push) Waiting to run

Async functions whose promise is never resolved were leaking, since they
had a strong root JS::Handle on themselves.

This doesn't appear to actually be necessary, since the wrapper will be
kept alive as long as it's reachable (and if it's not reachable, nobody
is going to resolve/reject the promise either).

This fixes the vast majority of leaks on Speedometer, bringing memory
usage at the end of a full run from ~12 GiB to ~3 GiB.
This commit is contained in:
Andreas Kling 2024-11-12 15:38:37 +01:00 committed by Andreas Kling
parent 10724a7cb3
commit b6a5b7e186
Notes: github-actions[bot] 2024-11-12 16:39:22 +00:00
2 changed files with 1 additions and 11 deletions

View file

@ -20,8 +20,7 @@ JS_DEFINE_ALLOCATOR(AsyncFunctionDriverWrapper);
NonnullGCPtr<Promise> AsyncFunctionDriverWrapper::create(Realm& realm, GeneratorObject* generator_object)
{
auto top_level_promise = Promise::create(realm);
// Note: This generates a handle to itself, which it clears upon completing its execution
// The top_level_promise is also kept alive by this Wrapper
// Note: The top_level_promise is also kept alive by this Wrapper
auto wrapper = realm.heap().allocate<AsyncFunctionDriverWrapper>(realm, realm, *generator_object, *top_level_promise);
// Prime the generator:
// This runs until the first `await value;`
@ -34,7 +33,6 @@ AsyncFunctionDriverWrapper::AsyncFunctionDriverWrapper(Realm& realm, NonnullGCPt
: Promise(realm.intrinsics().promise_prototype())
, m_generator_object(generator_object)
, m_top_level_promise(top_level_promise)
, m_self_handle(make_handle(*this))
{
}
@ -140,10 +138,6 @@ void AsyncFunctionDriverWrapper::continue_async_execution(VM& vm, Value value, b
auto promise_value = TRY(result.get(vm, vm.names.value));
if (TRY(result.get(vm, vm.names.done)).to_boolean()) {
// We should not execute anymore, so we are safe to allow ourselves to be GC'd.
m_self_handle = {};
// When returning a promise, we need to unwrap it.
if (promise_value.is_object() && is<Promise>(promise_value.as_object())) {
auto& returned_promise = static_cast<Promise&>(promise_value.as_object());
@ -175,9 +169,6 @@ void AsyncFunctionDriverWrapper::continue_async_execution(VM& vm, Value value, b
if (result.is_throw_completion()) {
m_top_level_promise->reject(result.throw_completion().value().value_or(js_undefined()));
// We should not execute anymore, so we are safe to allow our selfs to be GC'd
m_self_handle = {};
}
// For the initial execution, the execution context will be popped for us later on by ECMAScriptFunctionObject.

View file

@ -38,7 +38,6 @@ private:
NonnullGCPtr<GeneratorObject> m_generator_object;
NonnullGCPtr<Promise> m_top_level_promise;
GCPtr<Promise> m_current_promise { nullptr };
Handle<AsyncFunctionDriverWrapper> m_self_handle;
OwnPtr<ExecutionContext> m_suspended_execution_context;
};