-
Notifications
You must be signed in to change notification settings - Fork 51
Keep the Store
alive until its StoreContext
is no longer used
#206
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Conversation
As noted in bytecodealliance#200, this is required as otherwise it could theoretically happen that the `Store` handle is deleted (with `wasmtime_store_delete`) in the GC finalizer thread even when at the same time a native call using the `StoreContext` handle is still executing in another thread, in case the `Store` object is no longer reachable. For native methods taking a handle parameter that is passed as `SafeHandle`, this is not required as the `SafeHandle` is already kept alive during the call. An exception is if you use `SafeHandle.DangerousGetHandle()` to retrieve the handle as pointer value and pass it; in that case you must also keep the `SafeHandle` alive. This commit also fixes an instance of the above noted issue in `Engine.IncrementEpoch()`, where `wasmtime_engine_increment_epoch` was declared as taking an `IntPtr` handle, and `Engine.IncrementEpoch()` used `SafeHandle.DangerousGetHandle()` without keeping the SafeHandle alive (this appears to have been introduced with bytecodealliance#118).
529630b
to
9ec3413
Compare
Apologies on the delay for reviewing this as I was on vacation in December and I've been focusing a lot on tooling unrelated to the .NET bindings lately. I'll read up on the context and review the changes shortly. |
Wasmtime follows Rust's threading model where It further relies on Rust to maintain a guarantee that Some types, like The real problem with the .NET API is that it doesn't uphold the same guarantees with
There's two possible solutions in my mind for the second issue:
I haven't yet looked over the PR, but I am, on the surface, wary of injecting |
I followed up with #200 to say that I think that storing a weak self-reference in the store context data, as you originally described, would be a good path to move forward on for fixing the issue with I'm going to move forward with this review to fix the store lifetime issues at interop call sites. |
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 looks good and thanks for being so thorough!
* Allow to specify functions using untyped callbacks, which allows to specify a callback of any type without the need to use a specific delegate type. The untyped callback will receive arguments and can set results as a span of ValueBox. For example, this allows to define trapping functions for a module's import without having to know the function time at compile-time, similar to Wasmtime's define_unknown_imports_as_traps. * Follow-Up: Initialize the result ValueBoxes with their expected ValueKind but a default value, as otherwise they all would be initialized with ValueKind.Int32. * Follow-Up: Pull the (int) cast up, to align with InvokeCallback(). * Fix wording. * Enhance the tests to also check the ValueKind. * Fix code style. Co-authored-by: Peter Huene <peter@huene.dev> * Add missing GC.KeepAlive() call (which corresponds to the changes from #206). * Apply the changes from #202 also for the two remaining overloads of Linker.DefineFunction, to reduce allocations for the strings. * Follow-Up: Adjust for the changes in #211. Co-authored-by: Peter Huene <peter@huene.dev>
Keep the
Store
alive until itsStoreContext
is no longer used.As noted in #200 (comment), this is required (to handle the case when you forget to dispose the
Store
) as otherwise it could theoretically happen that theStore
handle is deleted (withwasmtime_store_delete
) in the GC finalizer thread even when at the same time a native call using theStoreContext
handle is still executing in another thread, in case theStore
object is no longer reachable.For native methods taking a handle parameter that is passed as
SafeHandle
, this is not required as theSafeHandle
is already kept alive during the call.An exception is if you use
SafeHandle.DangerousGetHandle()
to retrieve the handle as pointer value and pass it; in that case you must also keep theSafeHandle
alive.This commit also fixes an instance of the above noted issue in
Engine.IncrementEpoch()
, wherewasmtime_engine_increment_epoch
was declared as taking anIntPtr
handle, andEngine.IncrementEpoch()
usedSafeHandle.DangerousGetHandle()
without keeping the SafeHandle alive (this appears to have been introduced with #118).Note that I also added the
GC.KeepAlive(store)
if the store is used in method calls after that, because otherwise it is not guaranteed that the following call will actually keep the store alive (the call could be inlined, and if then doesn't read theStore
, it would still be eligible for GC); additionally, this would cause a "code debt" if you edit the code in the future, as e.g. when you would change/remove the code after the call using theStoreContext
, you would have to remember to add theGC.KeepAlive(store)
call.Note: A general question that came to my mind, is whether
Wasmtime
actually has a thread-safe resource management, i.e. whether it supports that one thread may be freeing a resource, while at the same time another thread is calling a native method that uses another value that internally might contain a reference to the resource freed by the first thread.For example, there is the
Engine
object (wasm_engine_t
) that you need to create first, and then you can create aStore
(wasmtime_store_t*
) from theEngine
(aswasmtime_store_new()
takes awasm_engine_t*
parameter), from which I understand that internally, the returnedwasmtime_store_t
may contain a reference to thewasm_engine_t
.(So, if you call
wasm_engine_delete
to delete the engine while a store still exists, this will not actually free thewasm_engine_t
but just decrements its reference count. If you later delete thewasmtime_store_t
, then it and the referenced engine will actually be freed.)However, if in .NET code you create an
Engine
, and then create aStore
from theEngine
and then throw theEngine
away (and you forget to use ausing
block or to callEngine.Dispose()
afterwards), and later you callStore.Dispose()
, it can happen thatwasm_engine_delete()
is called from the GC finalizer thread, while at the same time your main thread is callingwasmtime_store_delete()
. Is this supported byWasmtime
?(If not, them I'm wondering whether the apporach of releasing handles in finalizers can actually be supported at all by
wasmtime-dotnet
.)What do you think?
Thanks!