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