Skip to content

Commit 5c68fe6

Browse files
authored
Add support for async tests to our OOM test infrastructure (#12953)
* Add support for async tests to our OOM test infrastructure * address review feedback
1 parent fc8dc5d commit 5c68fe6

File tree

1 file changed

+77
-31
lines changed

1 file changed

+77
-31
lines changed

crates/fuzzing/src/oom.rs

Lines changed: 77 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,16 @@
44
//! https://firefox-source-docs.mozilla.org/js/hacking_tips.html#how-to-debug-oomtest-failures
55
66
use 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

Comments
 (0)