44//! https://firefox-source-docs.mozilla.org/js/hacking_tips.html#how-to-debug-oomtest-failures
55
66use backtrace:: Backtrace ;
7- use std:: { alloc:: GlobalAlloc , cell:: Cell , mem, ptr, time} ;
8- use wasmtime_core:: error:: { Error , OutOfMemory , Result , bail} ;
7+ use std:: {
8+ alloc:: GlobalAlloc ,
9+ cell:: Cell ,
10+ mem,
11+ pin:: Pin ,
12+ ptr,
13+ task:: { Context , Poll } ,
14+ time,
15+ } ;
16+ use wasmtime_core:: error:: { OutOfMemory , Result , bail} ;
917
1018/// An allocator for use with `OomTest`.
1119#[ non_exhaustive]
@@ -46,28 +54,23 @@ fn set_oom_state(state: OomState) -> OomState {
4654
4755/// RAII helper to set the OOM state within a block of code and reset it upon
4856/// exiting that block (even if exiting via panic unwinding).
49- struct ScopedOomState {
50- prev_state : OomState ,
57+ struct ScopedOomState < ' a > {
58+ state : & ' a mut OomState ,
5159}
5260
53- impl ScopedOomState {
54- fn new ( state : OomState ) -> Self {
55- ScopedOomState {
56- prev_state : set_oom_state ( state) ,
57- }
58- }
59-
60- /// Finish this OOM state scope early, resetting the OOM state to what it
61- /// was before this scope was created, and returning the previous state that
62- /// was just overwritten by the reset.
63- fn finish ( & self ) -> OomState {
64- set_oom_state ( self . prev_state . clone ( ) )
61+ impl < ' a > ScopedOomState < ' a > {
62+ /// The OOM state will be initialized to `*state` on construction, and then
63+ /// `state` will be updated to be the old OOM state that existed when the
64+ /// original state is replaced on this type's drop.
65+ fn new ( state : & ' a mut OomState ) -> Self {
66+ * state = set_oom_state ( mem:: take ( state) ) ;
67+ ScopedOomState { state }
6568 }
6669}
6770
68- impl Drop for ScopedOomState {
71+ impl Drop for ScopedOomState < ' _ > {
6972 fn drop ( & mut self ) {
70- set_oom_state ( mem:: take ( & mut self . prev_state ) ) ;
73+ * self . state = set_oom_state ( mem:: take ( self . state ) ) ;
7174 }
7275}
7376
@@ -250,6 +253,12 @@ impl OomTest {
250253 /// Returns early once the test function returns `Ok(())` before an OOM has
251254 /// been injected.
252255 pub fn test ( & self , test_func : impl Fn ( ) -> Result < ( ) > ) -> Result < ( ) > {
256+ let future = self . test_async ( || async { test_func ( ) } ) ;
257+ crate :: block_on ( future)
258+ }
259+
260+ /// The same as `test` but `async`.
261+ pub async fn test_async ( & self , test_func : impl AsyncFn ( ) -> Result < ( ) > ) -> Result < ( ) > {
253262 let start = time:: Instant :: now ( ) ;
254263
255264 for i in 0 .. {
@@ -260,19 +269,18 @@ impl OomTest {
260269 }
261270
262271 log:: trace!( "=== Injecting OOM after {i} allocations ===" ) ;
263- let ( result, old_state) = {
264- let guard = ScopedOomState :: new ( OomState :: OomOnAlloc {
272+
273+ let future = std:: pin:: pin!( test_func( ) ) ;
274+ let ( result, oom_state) = OomTestFuture :: new (
275+ future,
276+ OomState :: OomOnAlloc {
265277 counter : i,
266278 allow_alloc_after : self . allow_alloc_after_oom ,
267- } ) ;
268- assert_eq ! ( guard. prev_state, OomState :: OutsideOomTest ) ;
269-
270- let result = test_func ( ) ;
271-
272- ( result, guard. finish ( ) )
273- } ;
279+ } ,
280+ )
281+ . await ;
274282
275- match ( result, old_state ) {
283+ match ( result, oom_state ) {
276284 ( _, OomState :: OutsideOomTest ) => unreachable ! ( ) ,
277285
278286 // The test function completed successfully before we ran out of
@@ -281,7 +289,7 @@ impl OomTest {
281289
282290 // We injected an OOM and the test function handled it
283291 // correctly; continue to the next iteration.
284- ( Err ( e) , OomState :: DidOom { .. } ) if self . is_oom_error ( & e ) => { }
292+ ( Err ( e) , OomState :: DidOom { .. } ) if e . is :: < OutOfMemory > ( ) => { }
285293
286294 // Missed OOMs.
287295 ( Ok ( ( ) ) , OomState :: DidOom { .. } ) => {
@@ -304,8 +312,46 @@ impl OomTest {
304312
305313 Ok ( ( ) )
306314 }
315+ }
316+
317+ struct OomTestFuture < ' a , F > {
318+ inner : Pin < & ' a mut F > ,
319+ state : OomState ,
320+ }
307321
308- fn is_oom_error ( & self , e : & Error ) -> bool {
309- e. is :: < OutOfMemory > ( )
322+ impl < ' a , F > OomTestFuture < ' a , F > {
323+ fn new ( inner : Pin < & ' a mut F > , state : OomState ) -> Self {
324+ OomTestFuture { inner, state }
325+ }
326+ }
327+
328+ impl < F > Future for OomTestFuture < ' _ , F >
329+ where
330+ F : Future < Output = Result < ( ) > > ,
331+ {
332+ type Output = ( Result < ( ) > , OomState ) ;
333+
334+ fn poll ( mut self : Pin < & mut Self > , cx : & mut Context < ' _ > ) -> Poll < Self :: Output > {
335+ log:: trace!( "OomTestFuture::poll: Entered ---" ) ;
336+
337+ let result = {
338+ let OomTestFuture { inner, state } = & mut * self ;
339+
340+ let guard = ScopedOomState :: new ( state) ;
341+ assert_eq ! ( * guard. state, OomState :: OutsideOomTest ) ;
342+
343+ inner. as_mut ( ) . poll ( cx)
344+ } ;
345+
346+ log:: trace!( "OomTestFuture::poll: inner result: {result:?}" ) ;
347+ log:: trace!( "OomTestFuture::poll: old state: {:?}" , self . state) ;
348+
349+ match result {
350+ Poll :: Pending => {
351+ set_oom_state ( OomState :: OutsideOomTest ) ;
352+ Poll :: Pending
353+ }
354+ Poll :: Ready ( result) => Poll :: Ready ( ( result, mem:: take ( & mut self . state ) ) ) ,
355+ }
310356 }
311357}
0 commit comments