Files
wasmtime/tests/rlimited-memory.rs
Alex Crichton 52e90532e0 Add a limits and trap-on-OOM options to the CLI (#6149)
* Add a limits and trap-on-OOM options to the CLI

This commit adds new options to the `wasmtime` CLI to control the
`Store::limiter` behavior at runtime. This enables artificially
restriction the memory usage of the wasm instance, for example.
Additionally a new option is added to `StoreLimits` to force a trap on
growth failure. This is intended to help quickly debug modules with
backtraces if OOM is happening, or even diagnosing if OOM is happening
in the first place.

* Fix compile of fuzzing oracle
2023-04-05 17:26:36 +00:00

103 lines
3.0 KiB
Rust

// This test only works on Linux. It may be portable to MacOS as well,
// but the original author did not have a machine available to test it.
#![cfg(target_os = "linux")]
use anyhow::Result;
use wasmtime::*;
#[derive(Default)]
struct MemoryGrowFailureDetector {
current: usize,
desired: usize,
error: Option<String>,
}
impl ResourceLimiter for MemoryGrowFailureDetector {
fn memory_growing(
&mut self,
current: usize,
desired: usize,
_maximum: Option<usize>,
) -> Result<bool> {
self.current = current;
self.desired = desired;
Ok(true)
}
fn memory_grow_failed(&mut self, err: &anyhow::Error) {
self.error = Some(err.to_string());
}
fn table_growing(
&mut self,
_current: u32,
_desired: u32,
_maximum: Option<u32>,
) -> Result<bool> {
Ok(true)
}
}
#[test]
fn custom_limiter_detect_os_oom_failure() -> Result<()> {
if std::env::var("WASMTIME_TEST_NO_HOG_MEMORY").is_ok() {
return Ok(());
}
// Default behavior of on-demand memory allocation so that a
// memory grow will hit Linux for a larger mmap.
let engine = Engine::default();
let linker = Linker::new(&engine);
let module = Module::new(&engine, r#"(module (memory (export "m") 0))"#).unwrap();
// Ask Linux to limit this process to 256MiB of memory
let process_max_memory: usize = 256 * 1024 * 1024;
unsafe {
// limit process to 256MiB memory
let rlimit = libc::rlimit {
rlim_cur: 0,
rlim_max: process_max_memory as u64,
};
let res = libc::setrlimit(libc::RLIMIT_DATA, &rlimit);
assert_eq!(res, 0, "setrlimit failed: {}", res);
};
let context = MemoryGrowFailureDetector::default();
let mut store = Store::new(&engine, context);
store.limiter(|s| s as &mut dyn ResourceLimiter);
let instance = linker.instantiate(&mut store, &module).unwrap();
let memory = instance.get_memory(&mut store, "m").unwrap();
// Small (640KiB) grow should succeed
memory.grow(&mut store, 10).unwrap();
assert!(store.data().error.is_none());
assert_eq!(store.data().current, 0);
assert_eq!(store.data().desired, 10 * 64 * 1024);
// Try to grow past the process's memory limit.
// This should fail.
let pages_exceeding_limit = process_max_memory / (64 * 1024);
let err_msg = memory
.grow(&mut store, pages_exceeding_limit as u64)
.unwrap_err()
.to_string();
assert!(
err_msg.starts_with("failed to grow memory"),
"unexpected error: {}",
err_msg
);
assert_eq!(store.data().current, 10 * 64 * 1024);
assert_eq!(
store.data().desired,
(pages_exceeding_limit + 10) * 64 * 1024
);
// The memory_grow_failed hook should show Linux gave OOM:
let err_msg = store.data().error.as_ref().unwrap();
assert!(
err_msg.starts_with("Cannot allocate memory"),
"unexpected error: {}",
err_msg
);
Ok(())
}