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 cyclic ref in WasmerEnv #2327

Merged
merged 11 commits into from
May 29, 2021
Merged

Fix cyclic ref in WasmerEnv #2327

merged 11 commits into from
May 29, 2021

Conversation

MarkMcCaskey
Copy link
Contributor

@MarkMcCaskey MarkMcCaskey commented May 19, 2021

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 VMMemorys 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. WasmerEnvs are always passed by & and Cloneing 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 withWasmerEnv.

Review

  • Add a short description of the change to the CHANGELOG.md file

@MarkMcCaskey MarkMcCaskey changed the base branch from simplify-instance-ref to master May 20, 2021 15:41
@MarkMcCaskey MarkMcCaskey marked this pull request as ready for review May 25, 2021 19:38
instance_ref: self
.instance_ref
.as_ref()
.map(|v| WeakOrStrongInstanceRef::Strong(v.get_strong().unwrap())),
Copy link
Member

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?

Copy link
Member

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.

Copy link
Contributor Author

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(())
}
Copy link
Member

@syrusakbary syrusakbary May 26, 2021

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)

Copy link
Contributor Author

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.


/// 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...
Copy link
Member

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?)

Copy link
Contributor Author

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

Copy link
Contributor Author

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) {
Copy link
Member

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?)

Copy link
Contributor Author

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.

Copy link
Contributor Author

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/api/src/native.rs Outdated Show resolved Hide resolved
Comment on lines 225 to 228
/// Check if the reference contained is strong.
pub fn is_strong(&self) -> bool {
matches!(self, WeakOrStrongInstanceRef::Strong(_))
}
Copy link
Member

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?)

Copy link
Contributor Author

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() {
Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

Comment on lines +719 to +723
/// # 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)]
Copy link
Member

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

Comment on lines +750 to 760
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,
}
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Love this

@syrusakbary
Copy link
Member

bors r+

bors bot added a commit that referenced this pull request May 28, 2021
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 <mark@wasmer.io>
Co-authored-by: Syrus Akbary <me@syrusakbary.com>
Co-authored-by: Mark McCaskey <5770194+MarkMcCaskey@users.noreply.github.com>
@bors
Copy link
Contributor

bors bot commented May 28, 2021

Build failed:

@MarkMcCaskey
Copy link
Contributor Author

bors r+

@bors
Copy link
Contributor

bors bot commented May 29, 2021

@bors bors bot merged commit 99f42b0 into master May 29, 2021
@bors bors bot deleted the fix-cyclic-ref branch May 29, 2021 00:23
@kaimast
Copy link

kaimast commented May 29, 2021

Thanks for fixing this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

LazyInit<Memory> does not call munmap
3 participants