1 // This test only works on Linux. It may be portable to MacOS as well,
2 // but the original author did not have a machine available to test it.
3 #![cfg(target_os = "linux")]
4
5 use wasmtime::*;
6
7 #[derive(Default)]
8 struct MemoryGrowFailureDetector {
9 current: usize,
10 desired: usize,
11 error: Option<String>,
12 }
13
14 impl ResourceLimiter for MemoryGrowFailureDetector {
memory_growing( &mut self, current: usize, desired: usize, _maximum: Option<usize>, ) -> Result<bool>15 fn memory_growing(
16 &mut self,
17 current: usize,
18 desired: usize,
19 _maximum: Option<usize>,
20 ) -> Result<bool> {
21 self.current = current;
22 self.desired = desired;
23 Ok(true)
24 }
memory_grow_failed(&mut self, err: wasmtime::Error) -> Result<()>25 fn memory_grow_failed(&mut self, err: wasmtime::Error) -> Result<()> {
26 self.error = Some(err.to_string());
27 Ok(())
28 }
table_growing( &mut self, _current: usize, _desired: usize, _maximum: Option<usize>, ) -> Result<bool>29 fn table_growing(
30 &mut self,
31 _current: usize,
32 _desired: usize,
33 _maximum: Option<usize>,
34 ) -> Result<bool> {
35 Ok(true)
36 }
37 }
38
39 #[test]
40 #[cfg_attr(miri, ignore)]
41 #[cfg_attr(asan, ignore)]
custom_limiter_detect_os_oom_failure() -> Result<()>42 fn custom_limiter_detect_os_oom_failure() -> Result<()> {
43 if std::env::var("WASMTIME_TEST_NO_HOG_MEMORY").is_ok() {
44 return Ok(());
45 }
46
47 // Skip this test if it looks like we're in a cross-compiled situation,
48 // and we're emulating this test for a different platform. In that
49 // scenario QEMU ignores the data rlimit, which this test relies on. See
50 // QEMU commits 5dfa88f7162f ("linux-user: do setrlimit selectively") and
51 // 055d92f8673c ("linux-user: do prlimit selectively") for more
52 // information.
53 if wasmtime_test_util::cargo_test_runner().is_some() {
54 return Ok(());
55 }
56
57 // Default behavior of on-demand memory allocation so that a
58 // memory grow will hit Linux for a larger mmap.
59 let mut config = Config::new();
60 config.wasm_reference_types(false);
61 let engine = Engine::new(&config)?;
62 let linker = Linker::new(&engine);
63 let module = Module::new(&engine, r#"(module (memory (export "m") 0))"#).unwrap();
64
65 // Ask Linux to limit this process to 256MiB of memory
66 let process_max_memory: usize = 256 * 1024 * 1024;
67 unsafe {
68 // limit process to 256MiB memory
69 let rlimit = libc::rlimit {
70 rlim_cur: 0,
71 rlim_max: process_max_memory as libc::rlim_t,
72 };
73 let res = libc::setrlimit(libc::RLIMIT_DATA, &rlimit);
74 assert_eq!(res, 0, "setrlimit failed: {res}");
75 };
76
77 let context = MemoryGrowFailureDetector::default();
78
79 let mut store = Store::new(&engine, context);
80 store.limiter(|s| s as &mut dyn ResourceLimiter);
81 let instance = linker.instantiate(&mut store, &module).unwrap();
82 let memory = instance.get_memory(&mut store, "m").unwrap();
83
84 // Small (640KiB) grow should succeed
85 memory.grow(&mut store, 10).unwrap();
86 assert!(store.data().error.is_none());
87 assert_eq!(store.data().current, 0);
88 assert_eq!(store.data().desired, 10 * 64 * 1024);
89
90 // Try to grow past the process's memory limit.
91 // This should fail.
92 let pages_exceeding_limit = process_max_memory / (64 * 1024);
93 let err_msg = memory
94 .grow(&mut store, pages_exceeding_limit as u64)
95 .unwrap_err()
96 .to_string();
97 assert!(
98 err_msg.starts_with("failed to grow memory"),
99 "unexpected error: {err_msg}"
100 );
101
102 assert_eq!(store.data().current, 10 * 64 * 1024);
103 assert_eq!(
104 store.data().desired,
105 (pages_exceeding_limit + 10) * 64 * 1024
106 );
107 // The memory_grow_failed hook should show Linux gave OOM:
108 let err_msg = store.data().error.as_ref().unwrap();
109 assert!(
110 err_msg.starts_with("Cannot allocate memory"),
111 "unexpected error: {err_msg}"
112 );
113 Ok(())
114 }
115