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

Fix DisposeCapability leak #194

Merged
merged 7 commits into from Apr 10, 2024
Merged

Fix DisposeCapability leak #194

merged 7 commits into from Apr 10, 2024

Conversation

rbuckton
Copy link
Collaborator

@rbuckton rbuckton commented Jul 19, 2023

The DisposeResources AO fails to indicate that the resources in its [[DisposableResourceStack]] slot will no longer be used and can be freed. As a result, a DisposableStack will inadvertently hold resources in memory until the DisposableStack instance is freed, which is not an intended behavior.

This changes DisposeResources to set the [[DisposableResourceStack]] to a new empty list, along with a NOTE indicating the resources can be freed.

A PR against ecma262 is being tracked by rbuckton/ecma262#8.

Fixes #191

/cc: @tc39/ecma262-editors

@rbuckton rbuckton added needs-consensus A pull request that needs consensus at TC39 plenary normative Indicates a normative change to the specification labels Jul 19, 2023
@rbuckton rbuckton added this to the Stage 4 milestone Jul 19, 2023
@github-actions
Copy link

A preview of this PR can be found at https://tc39.es/proposal-explicit-resource-management/pr/194.

Copy link
Member

@mhofman mhofman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a result, a DisposableStack will inadvertently hold resources in memory until the DisposableStack instance is freed, which is not an intended behavior.

Yeah technically there is no future execution which would be able to observe the instance held in [[DisposableResourceStack]] of a disposed stack, so a strict reading of the definition of liveness would make this unnecessary, but I doubt any sane implementation would check whether the stack has already been disposed when doing gc, so making this explicit is better.

@rbuckton
Copy link
Collaborator Author

I've added this for discussion in the April 2024 plenary.

@syg
Copy link

syg commented Mar 29, 2024

Yeah technically there is no future execution which would be able to observe the instance held in [[DisposableResourceStack]] of a disposed stack, so a strict reading of the definition of liveness would make this unnecessary, but I doubt any sane implementation would check whether the stack has already been disposed when doing gc, so making this explicit is better.

I agree with @mhofman's reading. Strictly speaking, slots already don't keep things alive in and of themselves (we don't have reachability, we have liveness via observability).

Making a note doesn't hurt and makes the spec more readable. I'd call this an editorial fix, not a normative one.

spec.emu Outdated
@@ -1119,6 +1121,8 @@ contributors: Ron Buckton, Ecma International
1. Set _completion_ to ThrowCompletion(_error_).
1. Else,
1. Set _completion_ to _result_.
1. NOTE: After _disposeCapability_ has been disposed, it will never be used again. The contents of _disposeCapability_.[[DisposableResourceStack]] can be discarded at this point.
1. Set _disposeCapability_.[[DisposableResourceStack]] to *undefined*.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd set it to an empty list to avoid having to deal with a sentinel value everywhere else.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd set it to an empty list to avoid having to deal with a sentinel value everywhere else.

The upside of the sentinel value is that it makes things abundantly clear that a DisposeCapability is never intended to be reused, since we can assert on the absence of a list.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can also assert that the list is empty, no? In fact you can assert arbitrary English sentences, this isn't a program.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Asserting the list is empty would break DisposableStack/AsyncDisposableStack. Asserting a clearly defined state avoids ambiguity for spec readers and implementers. i.e.,

Assert: This DisposeCapability has never been passed as an argument to DisposeResources.

This seems way too ambiguous on its own.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Asserting the list is empty would break DisposableStack/AsyncDisposableStack

I don't follow, say more?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DisposableStack and AsyncDisposableStack also use DisposeCapability, which starts with an empty list. This should not cause an Assert, or even a TypeError:

const stack = new DisposableStack();
stack.dispose(); // DisposeCapability.[[DisposableResourceStack]] will be empty here.

In addition, using x = null does not add an entry to DisposeCapability.[[DisposableResourceStack]] either (only await using x = null does to ensure we trigger an Await), so an empty list is a perfectly valid state for a non-disposed DisposeCapability.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But you don't even need these asserts if you use an empty list. AFAICT the reasoning chain goes: you want to editorially convey that the list doesn't keep things alive -> you null it out with a sentinel value -> you add asserts that the sentinel values don't flow into places it shouldn't.

I still don't see what's lost by changing the above to: you want to editorially convey that the list doesn't keep things alive -> you empty the list. You get the same editorial effect, and fewer chances of editorial errors by flowing the sentinel into wrong places.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm concerned about the risk that a DisposeCapability is inadvertently reused. I am fine with changing this to just reset to an empty list, however.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you do want to use a sentinel value, you. might want to use something more evocative, like ~disposed~.

zloirock added a commit to zloirock/core-js that referenced this pull request Mar 30, 2024
@littledan
Copy link
Member

+1 to @syg and @mhofman -- this is not a leak currently. If we considered every internal slot which is left with things in it to be a memory leak, we'd have to change tons and tons of stuff in the spec. If we want to give implementations a hint, we could just note at the point where you'd set it to undefined that the contents will never be used again, and they could feel free to set it to undefined.

@rbuckton
Copy link
Collaborator Author

@syg, does the latest commit align with your suggestion?

@rbuckton rbuckton added the has-consensus Indicates a pull request reached consensus at TC39 plenary. label Apr 10, 2024
Copy link

@syg syg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm. Added a suggestion to call out GC specifically since that was the editorial intent.

spec.emu Outdated Show resolved Hide resolved
@rbuckton rbuckton merged commit 7291299 into main Apr 10, 2024
1 check passed
@rbuckton rbuckton deleted the fix-disposecapability-leak-1 branch April 10, 2024 17:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
has-consensus Indicates a pull request reached consensus at TC39 plenary. needs-consensus A pull request that needs consensus at TC39 plenary normative Indicates a normative change to the specification
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Should [[DisposableResourceStack]] be cleared after dispose?
5 participants