-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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 GC for references and hold parameter references over async await #3743
Closed
pando-fredrik
wants to merge
7
commits into
rustwasm:main
from
pando-fredrik:gc-for-refs-and-hold-values-over-async
Closed
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
0d4b2b7
Reenable GC for references, hold values over async points
pando-fredrik 03c393c
rustfmt
pando-fredrik 7f40be6
Only generate glue if there are args
pando-fredrik 4c2b391
Only generate if weakrefs are enabled
pando-fredrik 7fb9fb1
clippy fix
pando-fredrik 1de7e70
Fixed typo and removed unnecessary uint conversion
Na1w 6d99073
Don't keep references to functions
Na1w File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't the right place to put this, because it's too low-level: all of the arguments have already been converted to numbers so that they can be passed to Wasm, and the return type of
wasm.{}
is a number as well. This means that:.then()
, etc. won't work since it isn't a promise yet, just a number.The proper way to do this would be to add a new
Descriptor
for futures, since they're no longer treated the same as regularJsValue
s and should be a separate type. Then the JS that creates promises from raw numbers would be the same as the JS that createsJsValue
s from raw numbers, except with this tacked on the end.A different way to accomplish this would be to add reference counting inside Rust. Then, when JavaScript's handle to the Rust struct gets GC'd, it doesn't actually free the struct yet if Rust is still using it; it would just decrement the reference count from 2 to 1. Then when Rust is done with it, it'd go from 1 to 0 and the struct would actually be freed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While you're probably right (and especially in the mut ref case), I fail to see what you're talking about regarding arguments being numbers (when compiled with external refs)
For example
The unmodified generated glue code looks like this; (params is the object that gets garbage collected)
While the patched version generates this- So "params" in this case is a javascript object (although a Rust class in this case) (and so is this since the PR referred to in the PRs original description), notice the retained object is the root object not the __wbg_ptr etc that are indeed numeric, yes you're correct the return value is not a promise when at least the function signature is mut ref (it generates an additional getObject() accessor that does not appear to occur in other cases)
As for the suggestion to use reference counting it could work as this is the glue code (but I'm bit concerned about dependency cycles, unless the counting is done here as well),
However perhaps I was unclear exactly when and why this is an issue with the current wasm-bindgen.
Given the classes above RequestParamObject and Context (with the async method request)
This will work as expected with the current (non patched implementation, except it doesn't garbage collect the rust heap for RequestParamObject when it goes out of scope with 0.2.88/0.2.89)
However if the code is written like this (which it unfortunately is in some third party applications that use our library)
Just to clarify; it's the "req" object that will be registered to the FinalizationRegistry and is the object that will be garbage collected early (which in the current implementation results in the Drop of the RequestParamObject is invoked and triggers the WasmRefCells reference counting as the "performRequest" function has not yet returned and this has an outstanding non mutable access to the object, while the Drop requests a mutable reference which triggers the "rust does not allow aliasing" exception to be throw.
But are you confident this can be resolved using a Descriptor for futures?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, sorry, I missed that you strip the
.__wbg_ptr
insidefind_root_objects
. My bad. I'm... not particularly a fan of using string manipulation like that, it feels a bit brittle to me if e.g. the pointer gets moved into a variable for whatever reason, but I'll ignore that for now.I didn't realise you were compiling with reference types enabled; that's probably why you didn't notice this. When it's disabled,
getObject()
is always used, and so the return value is never already a promise.I don't think it should be possible to create dependency cycles. What I was proposing is to change the way exported Rust structs are stored in Rust from a
Box<WasmRefCell<T>>
to anRc<WasmRefCell<T>>
. Since thatRc
is completely private, I don't think there's any way someone could get access to it and include it insideT
to create a cycle.It would be possible for it to contain a
JsValue
of its own JS wrapper, stopping it from being GC'd; but you can already do that today, that's nothing new.Yeah; to be honest though I think reference-counting is a better solution, and should be significantly simpler to implement.
I wasn't very clear with what I meant by 'add a
Descriptor
' though, so let me elaborate on that a bit.Right now, the list of
Instruction
s generated for an async function looks something like this:Right now the anti-GC code is being generated inside
CallExport
, when we want it to be called afterExternrefLoadOwned
. So, we need to add some extra instructions onto the end when the function is async:But then, we need some way to communicate to the function that generates instructions (
Context::register_export_adapter
) that the function it's processing is async, so that it can add those instructions. This is why I was suggesting to add aDescriptor
; as a way of communicating that, which fits within the existing infrastructure and doesn't require e.g. threading anasyncness
parameter down to it.Again though I think it's probably a better idea to do reference counting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your detailed explaination, I'll look into the Descriptor approach (and I'll explain why below) I understand and would also prefer making the wasmrefcell reference counted however I believe we're trying to solve two different problems here.
So let me reiterate once again.
So while I agree that ref counting on the wasm side would be a clean and relatively simple solution I don't see how it would eliminate the issue (unless you make your API so it may only accept wasm objects as input parameters to asynch methods if compiling with external-refs of course, but that would be rather intrusive I think?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Huh, that's surprising. I assume by 'javascript objects' you mean
JsValue
s and other imported JS types?I wouldn't have thought that was possible, since all the JS values Rust has access to are kept in a global array (
heap
), which should stop them getting GC'd. I'd appreciate it if you could describe how to reproduce this so I can figure out what's going on, but if that's true then I agree that the Descriptor approach makes more sense.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First of all happy holidays, I apoligize for the late reply.
I'll retract my statement above and can only conclude that I must have confused myself when I was troubleshooting the issue.
I now made a standalone project (although not uploaded it) but you are absolutely correct, I'm not able to reproduce the problem with JsValues (external refs enabled), I don't see the JsValue being stored anywhere in the glue code though but it seems to generate a closure wrapper that does retain the reference.
So anyway either I confused myself and believed I saw something that never happened, or it's some special use case that I don't know how to get wasm-bindgen to generate in my test project- I'll disregard this for now and follow your other suggestion instead to make WasmRefCell reference counted instead. Thanks for your patience and detailed explaination, suggestions and ideas.