Fix a memory leak with command modules (#2017)

This commit fixes a memory leak that can happen with `Linker::module`
when the provided module is a command. This function creates a closure
but the closure closed over a strong reference to `Store` (and
transitively through any imports provided). Unfortunately a `Store`
keeps everything alive, including `Func`, so this meant that `Store` was
inserted into a cycle which caused the leak.

The cycle here is manually broken by closing over the raw value of each
external value rather than the external value itself (which has a
strong reference to `Store`).
This commit is contained in:
Alex Crichton
2021-02-01 13:04:27 -06:00
committed by GitHub
parent 23b8c6be79
commit cb7b1aadcc
4 changed files with 120 additions and 23 deletions

View File

@@ -1,4 +1,6 @@
use anyhow::Result;
use std::cell::Cell;
use std::rc::Rc;
use wasmtime::*;
#[test]
@@ -160,3 +162,64 @@ fn module_interposition() -> Result<()> {
assert_eq!(func()?, 112);
Ok(())
}
#[test]
fn no_leak() -> Result<()> {
struct DropMe(Rc<Cell<bool>>);
impl Drop for DropMe {
fn drop(&mut self) {
self.0.set(true);
}
}
let flag = Rc::new(Cell::new(false));
{
let store = Store::default();
let mut linker = Linker::new(&store);
let drop_me = DropMe(flag.clone());
linker.func("", "", move || drop(&drop_me))?;
let module = Module::new(
store.engine(),
r#"
(module
(func (export "_start"))
)
"#,
)?;
linker.module("a", &module)?;
}
assert!(flag.get(), "store was leaked");
Ok(())
}
#[test]
fn no_leak_with_imports() -> Result<()> {
struct DropMe(Rc<Cell<bool>>);
impl Drop for DropMe {
fn drop(&mut self) {
self.0.set(true);
}
}
let flag = Rc::new(Cell::new(false));
{
let store = Store::default();
let mut linker = Linker::new(&store);
let drop_me = DropMe(flag.clone());
linker.func("", "", move || drop(&drop_me))?;
let module = Module::new(
store.engine(),
r#"
(module
(import "" "" (func))
(func (export "_start"))
)
"#,
)?;
linker.module("a", &module)?;
}
assert!(flag.get(), "store was leaked");
Ok(())
}