-
Notifications
You must be signed in to change notification settings - Fork 824
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 cyclic ref in WasmerEnv #2327
Conversation
lib/vm/src/export.rs
Outdated
instance_ref: self | ||
.instance_ref | ||
.as_ref() | ||
.map(|v| WeakOrStrongInstanceRef::Strong(v.get_strong().unwrap())), |
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.
Cloning it automatically make ti strong?
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.
It's a bit weird this behavior. Perhaps we want to be more explicit having a custom method for this saying: into_strong
that makes it strong.
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.
Yeah, was unsure to what extent we want to encapsulate that logic, but this is the secret sauce that makes this solution invisible to the user and behave correctly in every situation. Weak externs automatically become strong when they're shared (via cloning)
f.call()?; | ||
|
||
Ok(()) | ||
} |
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.
We are testing the strong/weak behavior directly (is_strong_instance_ref
) which is great. (Edit: we should move that functions into the test file directly, they don't seem to be used outside of this file)
But can we have an extra test that verifies the behavior is fixed without checking for strong/weak fields? (that means, without assuming the design the API)
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.
It's not exactly easy to test because it's a memory leak test, it is possible to run some logic in a loop and if it crashes due to OOM then it triggers. My concern about that is that changes to the system (like how much virtual / real memory is available) may change when/if that test passes / fails.
lib/api/src/externals/global.rs
Outdated
|
||
/// Check if the global holds a strong `InstanceRef`. | ||
/// None means there's no `InstanceRef`, strong or weak. | ||
// TODO: maybe feature gate this, we only need it for tests... |
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 is only used in tests. As such I would expect to only be visible there (can we delete it from here and move the logic somewhere else?)
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.
Unfortunately it can't be, these accessors access private info. It has to be a method on the type
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.
One solution to this would be to define a trait with these methods on it and export it in like wasmer::test
and mark that module as #[hidden]
so it doesn't show up in docs. Then these methods will only be available if you import wasmer::test::SomeTrait
@@ -72,6 +72,15 @@ impl<'a> Exportable<'a> for Extern { | |||
// Since this is already an extern, we can just return it. | |||
Ok(_extern) | |||
} | |||
|
|||
fn into_weak_instance_ref(&mut self) { |
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.
I'm not super fan of this function name. I think we can just have into_weak
(the API user should not care about how is implemented?)
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.
Yeah, the reason for the extra precision is due to overlap in ownership. It's not really a weak version of the thing it's being called on. That thing can still keep things alive. The only thing being made weak here is an internal InstanceRef
. So I do think to that extent, it does matter, it's generally not something users should have to interact with unless they're doing low level things.
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.
Looking at this function again, we probably need to make it unsafe because you can probably get use after free with it. I'll investigate this with a test and maybe update it
lib/vm/src/instance/ref.rs
Outdated
/// Check if the reference contained is strong. | ||
pub fn is_strong(&self) -> bool { | ||
matches!(self, WeakOrStrongInstanceRef::Strong(_)) | ||
} |
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 is only used in tests. I believe we can move the logic out of this trait (perhaps into the tests?)
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.
Yeah, it could be a utility function in tests / in the methods of all the types that use it, it seems like a useful method to have on a type like this though
impl MemoryUsage for WeakInstanceRef { | ||
fn size_of_val(&self, tracker: &mut dyn MemoryUsageTracker) -> usize { | ||
mem::size_of_val(self) | ||
+ if let Some(ir) = self.upgrade() { |
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.
Getting the size of a value makes it strong? That seems wrong.
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.
Yeah, weak references can't be accessed. The only way to look into this type and see how much memory might be being used is to try to make it into a strong reference. If it can become a strong reference then that memory exists and we can count it, if it's not then we can't.
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.
And to clarify it doesn't mutate the internal value, it just needs to upgrade the reference in order to count correctly. This logic should be implemented inside of loupe itself on the Weak
type, not here.
Expose inner VM externals as an unsafe method and move logic into the tests
/// # Safety | ||
/// This function is unsafe to call outside of tests for the wasmer crate | ||
/// because there is no stability guarantee for the returned type and we may | ||
/// make breaking changes to it at any time or remove this method. | ||
#[doc(hidden)] |
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.
Good call on the unsafe
to discourage usage
impl Clone for Function { | ||
fn clone(&self) -> Self { | ||
let mut exported = self.exported.clone(); | ||
exported.vm_function.upgrade_instance_ref().unwrap(); | ||
|
||
Self { | ||
store: self.store.clone(), | ||
exported, | ||
} | ||
} | ||
} |
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.
Love this
bors r+ |
2327: Fix cyclic ref in WasmerEnv r=syrusakbary a=MarkMcCaskey This is an alternative to #2312 to fix #2304 The core idea is to have a `WeakInstanceRef` too. The main concern is that `Memory` (from a `WasmerEnv`) should behave like it has a strong instance ref if it's cloned. Currently we always convert weak `VMMemory`s into strong ones and if we can't, we panic. 1 other edge case not handled yet: we need to make sure that when the `InstanceRef` is weak that everything still works as expected (for example when the `Instance` it points to is gone). Or maybe we don't. `WasmerEnv`s are always passed by `&` and `Clone`ing forces them to upgrade into `Strong` or panic. So there's no real case where this can happen except for when you're calling into a host function from Wasm while the Instance is freed. Doing that means that you're already executing uninitialized memory as code so it shouldn't be possible / if it is, the concern is not with`WasmerEnv`. # Review - [ ] Add a short description of the change to the CHANGELOG.md file Co-authored-by: Mark McCaskey <[email protected]> Co-authored-by: Syrus Akbary <[email protected]> Co-authored-by: Mark McCaskey <[email protected]>
Build failed: |
bors r+ |
Thanks for fixing this! |
This is an alternative to #2312 to fix #2304
The core idea is to have a
WeakInstanceRef
too. The main concern is thatMemory
(from aWasmerEnv
) should behave like it has a strong instance ref if it's cloned. Currently we always convert weakVMMemory
s into strong ones and if we can't, we panic.1 other edge case not handled yet: we need to make sure that when the
InstanceRef
is weak that everything still works as expected (for example when theInstance
it points to is gone). Or maybe we don't.WasmerEnv
s are always passed by&
andClone
ing forces them to upgrade intoStrong
or panic. So there's no real case where this can happen except for when you're calling into a host function from Wasm while the Instance is freed. Doing that means that you're already executing uninitialized memory as code so it shouldn't be possible / if it is, the concern is not withWasmerEnv
.Review