#![cfg(windows)]
use std::collections::HashMap;
use std::fs::File;
use std::os::windows::io::{AsRawHandle, OwnedHandle};
use std::path::PathBuf;
use windows_ioring_sys::contract::RingContract;
use windows_ioring_sys::{
Batch, Completion, FlushCoverage, FlushMode, IoRing, PushOptions, RegisteredBuffers,
RegisteredFile, RegisteredSpan, RegisteredUse, SharedFile, Token, WriteCaching,
};
use windows_sys::Win32::Foundation::{WAIT_OBJECT_0, WAIT_TIMEOUT};
use windows_sys::Win32::System::Threading::WaitForSingleObject;
const POP_BOUND: std::time::Duration = std::time::Duration::from_secs(10);
#[global_allocator]
static ALLOC: windows_guard_alloc::GuardAlloc = windows_guard_alloc::GuardAlloc::new();
const SEED_VAR: &str = "WINDOWS_IORING_SEQ_SEED";
const SEQUENCES: usize = 128;
const MAX_STEPS: usize = 10;
const BUF_LEN: usize = 512;
const REGISTERED_BUFFERS: u32 = 4;
const WRITE_BASE: u64 = 64 * BUF_LEN as u64;
const FILE_LEN: usize = 128 * BUF_LEN;
const MAX_DRAIN_ROUNDS: usize = 4096;
const WAIT_MS: u32 = 5_000;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
enum OpKind {
Read,
Write,
Flush,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
enum TargetKind {
Raw,
Shared,
Registered,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
enum BufferKind {
Owned,
Registered,
}
#[derive(Default)]
struct Coverage {
shapes: std::collections::HashSet<(OpKind, TargetKind, BufferKind)>,
deliberate_drops: usize,
attaches: usize,
attaches_with_work_outstanding: usize,
mid_sequence_drains: usize,
buffer_downgrades: usize,
operations: usize,
}
#[derive(Clone, Copy, Debug)]
struct GenOp {
kind: OpKind,
target: TargetKind,
buffer: BufferKind,
claim: bool,
drain_preceding: bool,
slot: u64,
}
#[derive(Clone, Copy, Debug)]
enum Step {
Op(GenOp),
DrainNow,
Attach,
}
#[derive(Clone, Debug)]
struct Plan {
steps: Vec<Step>,
}
struct Rng(u64);
impl Rng {
fn next_u64(&mut self) -> u64 {
self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = self.0;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
fn below(&mut self, bound: u64) -> u64 {
self.next_u64() % bound
}
fn chance(&mut self, percent: u64) -> bool {
self.below(100) < percent
}
}
fn resolve_seed() -> u64 {
match std::env::var(SEED_VAR) {
Ok(text) => {
let trimmed = text.trim();
let parsed = trimmed
.strip_prefix("0x")
.or_else(|| trimmed.strip_prefix("0X"))
.map_or_else(
|| trimmed.parse::<u64>().ok(),
|hex| u64::from_str_radix(hex, 16).ok(),
);
parsed.unwrap_or_else(|| {
panic!("{SEED_VAR} is set to {text:?}, which is neither decimal nor 0x hex")
})
}
Err(_) => std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(0x243F_6A88_85A3_08D3, |elapsed| elapsed.as_nanos() as u64),
}
}
fn generate(rng: &mut Rng) -> Plan {
let step_count = 3 + rng.below(MAX_STEPS as u64 - 2) as usize;
let mut steps = Vec::with_capacity(step_count);
let attach_at = if rng.chance(60) {
Some(rng.below(step_count as u64) as usize)
} else {
None
};
for index in 0..step_count {
if attach_at == Some(index) {
steps.push(Step::Attach);
}
if rng.chance(20) {
steps.push(Step::DrainNow);
continue;
}
let kind = match rng.below(10) {
0..=5 => OpKind::Read,
6..=8 => OpKind::Write,
_ => OpKind::Flush,
};
let target = match rng.below(3) {
0 => TargetKind::Raw,
1 => TargetKind::Shared,
_ => TargetKind::Registered,
};
let buffer = if kind == OpKind::Flush || rng.chance(45) {
BufferKind::Owned
} else {
BufferKind::Registered
};
steps.push(Step::Op(GenOp {
kind,
target,
buffer,
claim: rng.chance(85),
drain_preceding: rng.chance(15),
slot: rng.below(16),
}));
}
Plan { steps }
}
enum Held {
RawOwned(Token<Vec<u8>>),
RawRegistered(Token<RegisteredUse>),
SharedOwned(Token<(Vec<u8>, SharedFile)>),
SharedRegistered(Token<(RegisteredUse, SharedFile)>),
SharedFlush(Token<SharedFile>),
RegdOwned(Token<(Vec<u8>, RegisteredFile)>),
RegdRegistered(Token<(RegisteredUse, RegisteredFile)>),
RegdFlush(Token<RegisteredFile>),
}
impl Held {
fn claim(self, completion: &Completion) -> Result<(), ()> {
match self {
Held::RawOwned(token) => token.claim_if(completion).map(|_| ()).map_err(|_| ()),
Held::RawRegistered(token) => token.claim_if(completion).map(|_| ()).map_err(|_| ()),
Held::SharedOwned(token) => token.claim_if(completion).map(|_| ()).map_err(|_| ()),
Held::SharedRegistered(token) => token.claim_if(completion).map(|_| ()).map_err(|_| ()),
Held::SharedFlush(token) => token.claim_if(completion).map(|_| ()).map_err(|_| ()),
Held::RegdOwned(token) => token.claim_if(completion).map(|_| ()).map_err(|_| ()),
Held::RegdRegistered(token) => token.claim_if(completion).map(|_| ()).map_err(|_| ()),
Held::RegdFlush(token) => token.claim_if(completion).map(|_| ()).map_err(|_| ()),
}
}
}
struct Run {
contract: RingContract,
held: HashMap<usize, Held>,
dropped: HashMap<usize, ()>,
buffer_in_use: HashMap<usize, u32>,
trace: Vec<String>,
}
fn temp_file(tag: &str) -> PathBuf {
std::env::temp_dir().join(format!(
"windows-ioring-sys-generated-{tag}-{}.tmp",
std::process::id()
))
}
fn fixture(tag: &str) -> (File, PathBuf) {
let path = temp_file(tag);
let mut content = vec![0_u8; FILE_LEN];
for (index, chunk) in content.chunks_mut(BUF_LEN).enumerate() {
chunk.fill(index as u8);
}
std::fs::write(&path, &content).expect("write fixture file");
let file = std::fs::OpenOptions::new()
.read(true)
.write(true)
.open(&path)
.expect("open fixture read/write");
(file, path)
}
fn duplicate_handle(file: &File) -> OwnedHandle {
file.try_clone()
.expect("duplicate the fixture handle")
.into()
}
fn drain_to_empty(ring: &mut IoRing, run: &mut Run) -> usize {
let mut popped = 0;
while let Some(completion) = ring.try_pop().expect("pop completion") {
let user_data = completion.user_data();
run.contract.observe_completion(user_data);
let _ = completion.result();
run.buffer_in_use.remove(&user_data);
if let Some(held) = run.held.remove(&user_data) {
held.claim(&completion)
.expect("a token must claim its own completion");
run.contract.observe_claim(user_data);
} else if run.dropped.remove(&user_data).is_some() {
run.contract.observe_deliberate_leak(user_data);
}
popped += 1;
}
popped
}
fn run_plan(
plan: &Plan,
file: &File,
shared: &SharedFile,
coverage: &mut Coverage,
) -> Result<(), String> {
let handle = file.as_raw_handle();
let mut ring = IoRing::new(64, 64).expect("create ring");
let mut run = Run {
contract: RingContract::new(),
held: HashMap::new(),
dropped: HashMap::new(),
buffer_in_use: HashMap::new(),
trace: Vec::new(),
};
let registered_file = {
let mut batch = Batch::new(&mut ring);
let pending = unsafe { batch.register_files(&[handle]) }.expect("queue file registration");
batch.submit_and_wait(1, 5_000).expect("submit");
let completion = ring
.pop_within(POP_BOUND)
.expect("pop")
.expect("a registration completion arrives within the bound");
pending
.claim_if(&completion)
.expect("registration token claims its own completion")
.expect("file registration succeeded")
};
let registered_file = registered_file.get(0).expect("index 0 exists");
let registered_buffers: RegisteredBuffers<Vec<u8>> = {
let mut batch = Batch::new(&mut ring);
let pending = batch
.register_buffers(
(0..REGISTERED_BUFFERS)
.map(|_| vec![0_u8; BUF_LEN])
.collect(),
)
.expect("queue buffer registration");
batch.submit_and_wait(1, 5_000).expect("submit");
let completion = ring
.pop_within(POP_BOUND)
.expect("pop")
.expect("a registration completion arrives within the bound");
pending
.claim_if(&completion)
.expect("registration token claims its own completion")
.expect("buffer registration succeeded")
};
let mut event: Option<OwnedHandle> = None;
for step in &plan.steps {
match step {
Step::DrainNow => {
coverage.mid_sequence_drains += 1;
match wait_then_drain(&mut ring, &mut run, event.as_ref(), WAIT_MS) {
Ok(popped) => run.trace.push(format!("drain to empty ({popped} popped)")),
Err(lost) => {
run.trace.push("drain to empty".to_owned());
let report = format!("{lost}\ntrace:\n{}", render(&run.trace));
forfeit(registered_buffers);
return Err(report);
}
}
}
Step::Attach => {
coverage.attaches += 1;
if ring.outstanding() > 0 {
coverage.attaches_with_work_outstanding += 1;
}
run.trace.push(format!(
"attach completion event (queue had {} outstanding)",
ring.outstanding()
));
event = Some(ring.completion_event().expect("attach completion event"));
}
Step::Op(op) => {
submit_one(
&mut ring,
&mut run,
op,
handle,
shared,
registered_file,
®istered_buffers,
coverage,
);
}
}
}
let mut rounds = 0;
while ring.outstanding() > 0 {
rounds += 1;
if rounds > MAX_DRAIN_ROUNDS {
let report = format!(
"{} operations never completed after {rounds} drain rounds\ntrace:\n{}",
ring.outstanding(),
render(&run.trace)
);
forfeit(registered_buffers);
return Err(report);
}
if let Err(lost) = wait_then_drain(&mut ring, &mut run, event.as_ref(), WAIT_MS) {
let report = format!("{lost}\ntrace:\n{}", render(&run.trace));
forfeit(registered_buffers);
return Err(report);
}
}
drain_to_empty(&mut ring, &mut run);
for index in 0..REGISTERED_BUFFERS {
if let Some(outstanding) = registered_buffers.outstanding(index) {
run.contract.observe_buffer(index, outstanding);
}
}
let violations = run.contract.check_quiescent();
drop(event);
if violations.is_empty() {
Ok(())
} else {
let report = format!("{violations:?}\ntrace:\n{}", render(&run.trace));
forfeit(registered_buffers);
Err(report)
}
}
fn forfeit(registration: RegisteredBuffers<Vec<u8>>) {
std::mem::forget(registration);
}
fn signalled_within(event: &OwnedHandle, timeout_ms: u32) -> bool {
let result = unsafe { WaitForSingleObject(event.as_raw_handle(), timeout_ms) };
if result == WAIT_OBJECT_0 {
true
} else if result == WAIT_TIMEOUT {
false
} else {
panic!("unexpected WaitForSingleObject result 0x{result:08X}");
}
}
fn wait_then_drain(
ring: &mut IoRing,
run: &mut Run,
event: Option<&OwnedHandle>,
timeout_ms: u32,
) -> Result<usize, String> {
if ring.outstanding() == 0 {
return Ok(drain_to_empty(ring, run));
}
if let Some(event) = event
&& !signalled_within(event, timeout_ms)
{
return Err(format!(
"waited {timeout_ms} ms on an attached completion event with {} operations \
outstanding and was never woken -- a wakeup was lost",
ring.outstanding()
));
}
Ok(drain_to_empty(ring, run))
}
fn render(trace: &[String]) -> String {
trace
.iter()
.enumerate()
.map(|(index, line)| format!(" {index:2}. {line}\n"))
.collect()
}
#[allow(clippy::too_many_arguments)]
fn submit_one(
ring: &mut IoRing,
run: &mut Run,
op: &GenOp,
handle: std::os::windows::io::RawHandle,
shared: &SharedFile,
registered_file: RegisteredFile,
registered_buffers: &RegisteredBuffers<Vec<u8>>,
coverage: &mut Coverage,
) {
let free_index = (0..REGISTERED_BUFFERS)
.find(|index| !run.buffer_in_use.values().any(|in_use| in_use == index));
let buffer = match (op.buffer, free_index) {
(BufferKind::Registered, Some(_)) => BufferKind::Registered,
_ => BufferKind::Owned,
};
let span = free_index.map(|index| RegisteredSpan {
buffer_index: index,
offset: 0,
len: BUF_LEN as u32,
});
let claim = op.claim || buffer == BufferKind::Registered;
coverage.operations += 1;
coverage.shapes.insert((op.kind, op.target, buffer));
if op.buffer == BufferKind::Registered && buffer == BufferKind::Owned {
coverage.buffer_downgrades += 1;
}
if !claim {
coverage.deliberate_drops += 1;
}
let read_offset = (op.slot % 32) * BUF_LEN as u64;
let write_offset = WRITE_BASE + (op.slot % 32) * BUF_LEN as u64;
let options = PushOptions::new().drain_preceding(op.drain_preceding);
let mut batch = Batch::new(ring);
let held = match (op.kind, op.target, buffer) {
(OpKind::Flush, TargetKind::Raw, _) => {
let user_data =
unsafe { batch.flush_raw(handle, FlushCoverage::Unordered, FlushMode::Default) }
.expect("queue raw flush");
batch.submit().expect("submit");
run.contract.observe_tokenless_push(user_data);
return;
}
(OpKind::Flush, TargetKind::Shared, _) => batch
.flush(shared, FlushCoverage::Unordered, FlushMode::Default)
.map(Held::SharedFlush),
(OpKind::Flush, TargetKind::Registered, _) => batch
.flush(
®istered_file,
FlushCoverage::Unordered,
FlushMode::Default,
)
.map(Held::RegdFlush),
(OpKind::Read, TargetKind::Raw, BufferKind::Owned) => {
unsafe { batch.read_raw(handle, vec![0_u8; BUF_LEN], read_offset, options) }
.map(Held::RawOwned)
}
(OpKind::Read, TargetKind::Raw, BufferKind::Registered) => {
unsafe {
batch.read_registered_raw(
handle,
registered_buffers,
span.expect("a free registered buffer"),
read_offset,
options,
)
}
.map(Held::RawRegistered)
}
(OpKind::Read, TargetKind::Shared, BufferKind::Owned) => batch
.read(shared, vec![0_u8; BUF_LEN], read_offset, options)
.map(Held::SharedOwned),
(OpKind::Read, TargetKind::Shared, BufferKind::Registered) => batch
.read_registered(
shared,
registered_buffers,
span.expect("a free registered buffer"),
read_offset,
options,
)
.map(Held::SharedRegistered),
(OpKind::Read, TargetKind::Registered, BufferKind::Owned) => batch
.read(®istered_file, vec![0_u8; BUF_LEN], read_offset, options)
.map(Held::RegdOwned),
(OpKind::Read, TargetKind::Registered, BufferKind::Registered) => batch
.read_registered(
®istered_file,
registered_buffers,
span.expect("a free registered buffer"),
read_offset,
options,
)
.map(Held::RegdRegistered),
(OpKind::Write, TargetKind::Raw, BufferKind::Owned) => {
unsafe {
batch.write_raw(
handle,
vec![7_u8; BUF_LEN],
write_offset,
options,
WriteCaching::Cached,
)
}
.map(Held::RawOwned)
}
(OpKind::Write, TargetKind::Raw, BufferKind::Registered) => {
unsafe {
batch.write_registered_raw(
handle,
registered_buffers,
span.expect("a free registered buffer"),
write_offset,
options,
WriteCaching::Cached,
)
}
.map(Held::RawRegistered)
}
(OpKind::Write, TargetKind::Shared, BufferKind::Owned) => batch
.write(
shared,
vec![7_u8; BUF_LEN],
write_offset,
options,
WriteCaching::Cached,
)
.map(Held::SharedOwned),
(OpKind::Write, TargetKind::Shared, BufferKind::Registered) => batch
.write_registered(
shared,
registered_buffers,
span.expect("a free registered buffer"),
write_offset,
options,
WriteCaching::Cached,
)
.map(Held::SharedRegistered),
(OpKind::Write, TargetKind::Registered, BufferKind::Owned) => batch
.write(
®istered_file,
vec![7_u8; BUF_LEN],
write_offset,
options,
WriteCaching::Cached,
)
.map(Held::RegdOwned),
(OpKind::Write, TargetKind::Registered, BufferKind::Registered) => batch
.write_registered(
®istered_file,
registered_buffers,
span.expect("a free registered buffer"),
write_offset,
options,
WriteCaching::Cached,
)
.map(Held::RegdRegistered),
};
let held = held.expect("queue generated operation");
let user_data = match &held {
Held::RawOwned(t) => t.id(),
Held::RawRegistered(t) => t.id(),
Held::SharedOwned(t) => t.id(),
Held::SharedRegistered(t) => t.id(),
Held::SharedFlush(t) => t.id(),
Held::RegdOwned(t) => t.id(),
Held::RegdRegistered(t) => t.id(),
Held::RegdFlush(t) => t.id(),
};
batch.submit().expect("submit");
run.contract.observe_push(user_data);
run.trace.push(format!(
"{:?} target={:?} buffer={:?} {} drain_preceding={} offset={} user_data={user_data}",
op.kind,
op.target,
buffer,
if claim { "claim" } else { "DROP" },
op.drain_preceding,
if op.kind == OpKind::Write {
write_offset
} else {
read_offset
}
));
if buffer == BufferKind::Registered
&& let Some(span) = span
{
run.buffer_in_use.insert(user_data, span.buffer_index);
}
if claim {
run.held.insert(user_data, held);
} else {
drop(held);
run.dropped.insert(user_data, ());
}
}
#[test]
fn generated_sequences_satisfy_the_ring_contract() {
let seed = resolve_seed();
ALLOC.announce_seed();
eprintln!(
"generated_sequences: seed 0x{seed:016X}\n\
replay this run with:\n \
$env:{SEED_VAR}='0x{seed:016X}'; $env:WINDOWS_GUARD_ALLOC_SEED='0x{:016X}'; \
cargo test -p windows-ioring-sys --test generated_sequences",
ALLOC.seed()
);
assert!(
ALLOC.total_allocations() > 0,
"the guard-page allocator is not installed, so these sequences are running \
uninstrumented and only prove that nothing crashed"
);
let (file, path) = fixture("sweep");
let shared = SharedFile::new(duplicate_handle(&file));
let mut rng = Rng(seed);
let mut coverage = Coverage::default();
for sequence in 0..SEQUENCES {
let plan = generate(&mut rng);
if let Err(failure) = run_plan(&plan, &file, &shared, &mut coverage) {
panic!(
"sequence {sequence} of {SEQUENCES} violated the ring contract\n\
seed: 0x{seed:016X} (replay: $env:{SEED_VAR}='0x{seed:016X}')\n\
guard allocator seed: 0x{:016X}\n\
{failure}",
ALLOC.seed()
);
}
}
let mut missing = Vec::new();
for kind in [OpKind::Read, OpKind::Write] {
for target in [TargetKind::Raw, TargetKind::Shared, TargetKind::Registered] {
for buffer in [BufferKind::Owned, BufferKind::Registered] {
if !coverage.shapes.contains(&(kind, target, buffer)) {
missing.push(format!("{kind:?}/{target:?}/{buffer:?}"));
}
}
}
}
for target in [TargetKind::Raw, TargetKind::Shared, TargetKind::Registered] {
if !coverage
.shapes
.contains(&(OpKind::Flush, target, BufferKind::Owned))
{
missing.push(format!("Flush/{target:?}"));
}
}
assert!(
missing.is_empty(),
"seed 0x{seed:016X} produced {} operations but never exercised: {}\n\
the generator is sampling less of the space than this file claims",
coverage.operations,
missing.join(", ")
);
assert!(
coverage.deliberate_drops > 0,
"no token was ever dropped without claiming, so the claim-or-drop axis was not sampled"
);
assert!(
coverage.mid_sequence_drains > 0,
"no sequence ever drained mid-flight, so the drain-now-or-later axis was not sampled"
);
assert!(
coverage.attaches_with_work_outstanding > 0,
"the completion event was attached {} times but never to a ring with work outstanding, \
so #47's own axis was not sampled",
coverage.attaches
);
eprintln!(
"generated_sequences: {} sequences, {} operations, {} distinct shapes, \
{} deliberate drops, {} mid-sequence drains, {} attaches ({} with work outstanding), \
{} registered-buffer downgrades",
SEQUENCES,
coverage.operations,
coverage.shapes.len(),
coverage.deliberate_drops,
coverage.mid_sequence_drains,
coverage.attaches,
coverage.attaches_with_work_outstanding,
coverage.buffer_downgrades
);
drop(file);
let _ = std::fs::remove_file(&path);
}
struct Regression {
why: &'static str,
steps: Vec<Step>,
}
fn read(target: TargetKind, buffer: BufferKind, slot: u64) -> Step {
Step::Op(GenOp {
kind: OpKind::Read,
target,
buffer,
claim: true,
drain_preceding: false,
slot,
})
}
fn write(target: TargetKind, buffer: BufferKind, slot: u64) -> Step {
Step::Op(GenOp {
kind: OpKind::Write,
target,
buffer,
claim: true,
drain_preceding: false,
slot,
})
}
fn issue_47_backlog_at_handover() -> Regression {
Regression {
why: "a completion queued before the event was attached left a waiter with no wakeup, \
because the queue never returns to empty to re-arm the edge (D-19) and only D-20's \
deliberate setup signal covers the backlog",
steps: vec![
write(TargetKind::Registered, BufferKind::Registered, 4),
Step::Attach,
read(TargetKind::Registered, BufferKind::Owned, 1),
read(TargetKind::Registered, BufferKind::Registered, 1),
read(TargetKind::Registered, BufferKind::Registered, 5),
],
}
}
fn replay(tag: &str, regression: &Regression) {
let (file, path) = fixture(tag);
let shared = SharedFile::new(duplicate_handle(&file));
let mut coverage = Coverage::default();
let plan = Plan {
steps: regression.steps.clone(),
};
let outcome = run_plan(&plan, &file, &shared, &mut coverage);
drop(file);
let _ = std::fs::remove_file(&path);
if let Err(failure) = outcome {
panic!(
"regression `{tag}` reproduced a defect that was fixed\n\
what this sequence caught when it was found: {}\n\
{failure}",
regression.why
);
}
}
#[test]
fn regression_issue_47_backlog_at_handover() {
replay("issue-47", &issue_47_backlog_at_handover());
}
#[test]
fn the_lost_wakeup_detector_fires_when_no_wakeup_is_owed() {
const EXPECT_TIMEOUT_MS: u32 = 250;
let (file, path) = fixture("detector");
let handle = file.as_raw_handle();
let mut ring = IoRing::new(64, 64).expect("create ring");
let mut run = Run {
contract: RingContract::new(),
held: HashMap::new(),
dropped: HashMap::new(),
buffer_in_use: HashMap::new(),
trace: Vec::new(),
};
{
let mut batch = Batch::new(&mut ring);
let token = unsafe { batch.read_raw(handle, vec![0_u8; BUF_LEN], 0, PushOptions::new()) }
.expect("queue read");
run.contract.observe_push(token.id());
run.held.insert(token.id(), Held::RawOwned(token));
batch.submit().expect("submit");
}
let event = ring.completion_event().expect("attach completion event");
assert!(
signalled_within(&event, WAIT_MS),
"attaching must raise the deliberate setup signal (D-20); without it this test would \
pass for the wrong reason"
);
let outcome = wait_then_drain(&mut ring, &mut run, Some(&event), EXPECT_TIMEOUT_MS);
let report = outcome.expect_err(
"the detector reported no lost wakeup while waiting on a queue that cannot signal again \
-- it would be equally silent for #47",
);
assert!(
report.contains("a wakeup was lost"),
"the detector fired but did not say why: {report}"
);
drain_to_empty(&mut ring, &mut run);
run.contract.assert_quiescent();
drop(file);
let _ = std::fs::remove_file(&path);
}