use std::cell::RefCell;
use std::rc::Rc;
use simu::{any_of, PreemptiveResource, PriorityResource, Resource, SimEnv};
type Log = Rc<RefCell<Vec<String>>>;
fn new_log() -> Log {
Rc::new(RefCell::new(Vec::new()))
}
#[test]
fn resource_woken_fifo_waiter_beats_same_batch_fresh_request() {
let mut env = SimEnv::with_seed(0);
let log = new_log();
let res = Resource::new(1);
let (trig, sig) = env.event();
{
let r = res.clone();
let h = env.handle();
let log = log.clone();
let sig = sig.clone();
env.spawn(async move {
let g = r.request().await; sig.await; drop(g); log.borrow_mut().push(format!("P0 released @{}", h.now()));
});
}
{
let r = res.clone();
let h = env.handle();
let log = log.clone();
env.spawn(async move {
h.timeout(0.5).await;
let _g = r.request().await; log.borrow_mut().push(format!("A acquired @{}", h.now()));
h.timeout(1.0).await; });
}
{
let r = res.clone();
let h = env.handle();
let log = log.clone();
env.spawn(async move {
sig.await;
let g = r.request().await;
log.borrow_mut().push(format!("Q acquired @{}", h.now()));
h.timeout(1.0).await;
drop(g);
});
}
{
let h = env.handle();
env.spawn(async move {
h.timeout(1.0).await;
trig.fire();
});
}
env.run();
let log = log.borrow();
let a_pos = log.iter().position(|l| l.starts_with("A acquired"));
let q_pos = log.iter().position(|l| l.starts_with("Q acquired"));
assert!(a_pos.is_some(), "A was stranded (deadlock): {log:?}");
assert!(q_pos.is_some(), "Q was stranded: {log:?}");
assert!(
a_pos < q_pos,
"FIFO violated: A queued first but Q acquired first: {log:?}"
);
assert_eq!(res.in_use(), 0, "unit accounting drifted: {log:?}");
}
#[test]
fn priority_woken_waiter_beats_same_batch_fresh_request() {
let mut env = SimEnv::with_seed(0);
let log = new_log();
let res = PriorityResource::new(1);
let (trig, sig) = env.event();
{
let r = res.clone();
let h = env.handle();
let log = log.clone();
let sig = sig.clone();
env.spawn(async move {
let g = r.request(0).await;
sig.await;
drop(g);
log.borrow_mut().push(format!("P0 released @{}", h.now()));
});
}
{
let r = res.clone();
let h = env.handle();
let log = log.clone();
env.spawn(async move {
h.timeout(0.5).await;
let _g = r.request(0).await;
log.borrow_mut().push(format!("A acquired @{}", h.now()));
h.timeout(1.0).await;
});
}
{
let r = res.clone();
let h = env.handle();
let log = log.clone();
env.spawn(async move {
sig.await;
let g = r.request(0).await;
log.borrow_mut().push(format!("Q acquired @{}", h.now()));
h.timeout(1.0).await;
drop(g);
});
}
{
let h = env.handle();
env.spawn(async move {
h.timeout(1.0).await;
trig.fire();
});
}
env.run();
let log = log.borrow();
let a_pos = log.iter().position(|l| l.starts_with("A acquired"));
let q_pos = log.iter().position(|l| l.starts_with("Q acquired"));
assert!(a_pos.is_some(), "A was stranded (deadlock): {log:?}");
assert!(q_pos.is_some(), "Q was stranded: {log:?}");
assert!(a_pos < q_pos, "priority/FIFO violated: {log:?}");
assert_eq!(res.in_use(), 0);
}
#[test]
fn preemptive_woken_waiter_beats_same_batch_fresh_request() {
let mut env = SimEnv::with_seed(0);
let log = new_log();
let res = PreemptiveResource::new(1);
let (trig, sig) = env.event();
{
let r = res.clone();
let h = env.handle();
let log = log.clone();
let sig = sig.clone();
env.spawn(async move {
let g = r.request(0).await;
sig.await;
drop(g);
log.borrow_mut().push(format!("P0 released @{}", h.now()));
});
}
{
let r = res.clone();
let h = env.handle();
let log = log.clone();
env.spawn(async move {
h.timeout(0.5).await;
let _g = r.request(0).await;
log.borrow_mut().push(format!("A acquired @{}", h.now()));
h.timeout(1.0).await;
});
}
{
let r = res.clone();
let h = env.handle();
let log = log.clone();
env.spawn(async move {
sig.await;
let g = r.request(0).await;
log.borrow_mut().push(format!("Q acquired @{}", h.now()));
h.timeout(1.0).await;
drop(g);
});
}
{
let h = env.handle();
env.spawn(async move {
h.timeout(1.0).await;
trig.fire();
});
}
env.run();
let log = log.borrow();
let a_pos = log.iter().position(|l| l.starts_with("A acquired"));
let q_pos = log.iter().position(|l| l.starts_with("Q acquired"));
assert!(a_pos.is_some(), "A was stranded (deadlock): {log:?}");
assert!(q_pos.is_some(), "Q was stranded: {log:?}");
assert!(a_pos < q_pos, "FIFO violated: {log:?}");
assert_eq!(res.in_use(), 0);
}
#[test]
fn resource_granted_then_dropped_passes_unit_to_next_waiter() {
let mut env = SimEnv::with_seed(0);
let log = new_log();
let res = Resource::new(1);
{
let r = res.clone();
let h = env.handle();
env.spawn(async move {
let g = r.request().await;
h.timeout(5.0).await;
drop(g);
});
}
{
let r = res.clone();
let h = env.handle();
let log = log.clone();
env.spawn(async move {
h.timeout(1.0).await; let hi = h.clone();
let logi = log.clone();
any_of![
async move {
let _g = r.request().await;
logi.borrow_mut().push(format!("A acquired @{}", hi.now()));
hi.timeout(1.0).await;
},
h.timeout(4.0) ]
.await;
log.borrow_mut().push(format!("A raced-out @{}", h.now()));
});
}
{
let r = res.clone();
let h = env.handle();
let log = log.clone();
env.spawn(async move {
h.timeout(2.0).await; let _g = r.request().await;
log.borrow_mut().push(format!("B acquired @{}", h.now()));
h.timeout(1.0).await;
});
}
env.run();
let log = log.borrow();
assert!(
log.iter().any(|l| l.starts_with("B acquired")),
"B was stranded — a granted-then-dropped unit leaked: {log:?}"
);
assert_eq!(res.in_use(), 0, "double-release or leak: {log:?}");
}
#[test]
fn resource_granted_but_unconsumed_drop_passes_unit_on() {
let mut env = SimEnv::with_seed(0);
let log = new_log();
let res = Resource::new(1);
{
let r = res.clone();
let h = env.handle();
env.spawn(async move {
let g = r.request().await;
h.timeout(5.0).await;
drop(g);
});
}
{
let r = res.clone();
let h = env.handle();
let log = log.clone();
env.spawn(async move {
h.timeout(1.0).await; let hi = h.clone();
let logi = log.clone();
any_of![
h.timeout(4.0), async move {
let _g = r.request().await;
logi.borrow_mut().push(format!("A acquired @{}", hi.now()));
}
]
.await;
log.borrow_mut().push(format!("A raced-out @{}", h.now()));
});
}
{
let r = res.clone();
let h = env.handle();
let log = log.clone();
env.spawn(async move {
h.timeout(2.0).await; let _g = r.request().await;
log.borrow_mut().push(format!("B acquired @{}", h.now()));
h.timeout(1.0).await;
});
}
env.run();
let log = log.borrow();
assert!(
log.iter().any(|l| l.starts_with("A raced-out")),
"A never resolved its race: {log:?}"
);
assert!(
!log.iter().any(|l| l.starts_with("A acquired")),
"test setup drifted: A's request arm was re-polled before the timeout \
arm, so the granted-but-unconsumed drop path was not exercised: {log:?}"
);
assert!(
log.iter().any(|l| l.starts_with("B acquired")),
"B was stranded — the granted-but-unconsumed unit leaked: {log:?}"
);
assert_eq!(res.in_use(), 0, "double-release or leak: {log:?}");
}