use std::cell::RefCell;
use std::rc::Rc;
use simu::SimEnv;
use simu::{any_of, PreemptiveResource};
type Log = Rc<RefCell<Vec<String>>>;
fn new_log() -> Log {
Rc::new(RefCell::new(Vec::new()))
}
#[test]
fn acquire_immediately_when_free() {
let mut env = SimEnv::with_seed(0);
let res = PreemptiveResource::new(2);
let log = new_log();
{
let r = res.clone();
let log2 = log.clone();
env.spawn(async move {
let _g = r.request(0).await;
log2.borrow_mut().push(format!("in_use:{}", r.in_use()));
});
}
env.run();
assert_eq!(*log.borrow(), vec!["in_use:1"]);
assert_eq!(res.in_use(), 0);
assert_eq!(res.capacity(), 2);
}
#[test]
fn higher_priority_preempts_holder_immediately() {
let mut env = SimEnv::with_seed(0);
let res = PreemptiveResource::new(1);
let log = new_log();
{
let h = env.handle();
let r = res.clone();
let log2 = log.clone();
env.spawn(async move {
let guard = r.request(5).await;
log2.borrow_mut().push(format!("victim_start:{}", h.now()));
any_of![h.timeout(100.0), guard.preempted()].await;
if guard.is_preempted() {
log2
.borrow_mut()
.push(format!("victim_preempted:{}", h.now()));
} else {
log2.borrow_mut().push(format!("victim_done:{}", h.now()));
}
});
}
{
let h = env.handle();
let r = res.clone();
let log2 = log.clone();
env.spawn(async move {
h.timeout(1.0).await;
let _g = r.request(0).await;
log2.borrow_mut().push(format!("preemptor_got:{}", h.now()));
h.timeout(10.0).await;
});
}
env.run();
let log = log.borrow();
assert_eq!(log[0], "victim_start:0");
assert!(log.contains(&"victim_preempted:1".to_string()));
assert!(log.contains(&"preemptor_got:1".to_string()));
assert!(!log.iter().any(|l| l.starts_with("victim_done")));
assert_eq!(res.in_use(), 0);
}
#[test]
fn equal_priority_does_not_preempt() {
let mut env = SimEnv::with_seed(0);
let res = PreemptiveResource::new(1);
let log = new_log();
{
let h = env.handle();
let r = res.clone();
let log2 = log.clone();
env.spawn(async move {
let guard = r.request(1).await;
any_of![h.timeout(5.0), guard.preempted()].await;
log2
.borrow_mut()
.push(format!("holder_end:{} preempted:{}", h.now(), guard.is_preempted()));
});
}
{
let h = env.handle();
let r = res.clone();
let log2 = log.clone();
env.spawn(async move {
h.timeout(1.0).await;
let _g = r.request(1).await; log2.borrow_mut().push(format!("waiter_got:{}", h.now()));
});
}
env.run();
assert_eq!(
*log.borrow(),
vec!["holder_end:5 preempted:false", "waiter_got:5"]
);
}
#[test]
fn lower_priority_request_waits_for_release() {
let mut env = SimEnv::with_seed(0);
let res = PreemptiveResource::new(1);
let log = new_log();
{
let h = env.handle();
let r = res.clone();
env.spawn(async move {
let _g = r.request(0).await;
h.timeout(3.0).await;
});
}
{
let h = env.handle();
let r = res.clone();
let log2 = log.clone();
env.spawn(async move {
h.timeout(1.0).await;
let _g = r.request(2).await;
log2.borrow_mut().push(format!("low_got:{}", h.now()));
});
}
env.run();
assert_eq!(*log.borrow(), vec!["low_got:3"]);
}
#[test]
fn preempts_lowest_priority_holder_among_many() {
let mut env = SimEnv::with_seed(0);
let res = PreemptiveResource::new(2);
let log = new_log();
{
let h = env.handle();
let r = res.clone();
let log2 = log.clone();
env.spawn(async move {
let guard = r.request(3).await;
any_of![h.timeout(50.0), guard.preempted()].await;
log2
.borrow_mut()
.push(format!("A_end preempted:{}", guard.is_preempted()));
});
}
{
let h = env.handle();
let r = res.clone();
let log2 = log.clone();
env.spawn(async move {
let guard = r.request(8).await;
any_of![h.timeout(50.0), guard.preempted()].await;
log2
.borrow_mut()
.push(format!("B_end preempted:{}", guard.is_preempted()));
});
}
{
let h = env.handle();
let r = res.clone();
let log2 = log.clone();
env.spawn(async move {
h.timeout(1.0).await;
let _g = r.request(1).await;
log2.borrow_mut().push("preemptor_got".to_string());
h.timeout(5.0).await;
});
}
env.run();
let log = log.borrow();
assert!(log.contains(&"B_end preempted:true".to_string()));
assert!(log.contains(&"A_end preempted:false".to_string()));
assert!(log.contains(&"preemptor_got".to_string()));
}
#[test]
fn preemptor_blocks_when_no_victim_available() {
let mut env = SimEnv::with_seed(0);
let res = PreemptiveResource::new(1);
let log = new_log();
{
let h = env.handle();
let r = res.clone();
env.spawn(async move {
let _g = r.request(0).await;
h.timeout(4.0).await;
});
}
{
let h = env.handle();
let r = res.clone();
let log2 = log.clone();
env.spawn(async move {
h.timeout(1.0).await;
let _g = r.request(0).await;
log2.borrow_mut().push(format!("second_got:{}", h.now()));
});
}
env.run();
assert_eq!(*log.borrow(), vec!["second_got:4"]);
assert_eq!(res.in_use(), 0);
}
#[test]
fn preempted_guard_drop_does_not_double_release() {
let mut env = SimEnv::with_seed(0);
let res = PreemptiveResource::new(1);
let log = new_log();
{
let h = env.handle();
let r = res.clone();
let log2 = log.clone();
env.spawn(async move {
let guard = r.request(9).await;
any_of![h.timeout(100.0), guard.preempted()].await;
log2
.borrow_mut()
.push(format!("victim preempted:{}", guard.is_preempted()));
h.timeout(1.0).await;
drop(guard);
log2.borrow_mut().push(format!("victim_dropped in_use:{}", r.in_use()));
});
}
{
let h = env.handle();
let r = res.clone();
let log2 = log.clone();
env.spawn(async move {
h.timeout(1.0).await;
let _g = r.request(0).await;
h.timeout(10.0).await;
log2.borrow_mut().push(format!("preemptor_end in_use:{}", r.in_use()));
});
}
env.run();
let log = log.borrow();
assert!(log.contains(&"victim preempted:true".to_string()));
assert!(log.contains(&"victim_dropped in_use:1".to_string()));
assert_eq!(res.in_use(), 0);
}
#[test]
fn release_wakes_blocked_waiter_in_priority_order() {
let mut env = SimEnv::with_seed(0);
let res = PreemptiveResource::new(1);
let log = new_log();
{
let h = env.handle();
let r = res.clone();
env.spawn(async move {
let _g = r.request(0).await;
h.timeout(1.0).await;
});
}
{
let h = env.handle();
let r = res.clone();
let log2 = log.clone();
env.spawn(async move {
let _g = r.request(2).await;
log2.borrow_mut().push(format!("low:{}", h.now()));
h.timeout(1.0).await;
});
}
{
let h = env.handle();
let r = res.clone();
let log2 = log.clone();
env.spawn(async move {
let _g = r.request(1).await;
log2.borrow_mut().push(format!("high:{}", h.now()));
h.timeout(1.0).await;
});
}
env.run();
assert_eq!(*log.borrow(), vec!["high:1", "low:2"]);
}
#[test]
fn tie_break_preempts_most_recently_acquired() {
let mut env = SimEnv::with_seed(0);
let res = PreemptiveResource::new(2);
let log = new_log();
{
let h = env.handle();
let r = res.clone();
let log2 = log.clone();
env.spawn(async move {
let guard = r.request(5).await;
any_of![h.timeout(50.0), guard.preempted()].await;
log2
.borrow_mut()
.push(format!("A preempted:{}", guard.is_preempted()));
});
}
{
let h = env.handle();
let r = res.clone();
let log2 = log.clone();
env.spawn(async move {
let guard = r.request(5).await;
any_of![h.timeout(50.0), guard.preempted()].await;
log2
.borrow_mut()
.push(format!("B preempted:{}", guard.is_preempted()));
});
}
{
let h = env.handle();
let r = res.clone();
env.spawn(async move {
h.timeout(1.0).await;
let _g = r.request(0).await;
h.timeout(5.0).await;
});
}
env.run();
let log = log.borrow();
assert!(log.contains(&"B preempted:true".to_string()));
assert!(log.contains(&"A preempted:false".to_string()));
}
#[test]
#[should_panic(expected = "capacity must be at least 1")]
fn zero_capacity_panics() {
let _ = PreemptiveResource::new(0);
}
#[test]
fn woken_waiter_losing_same_tick_race_is_not_starved() {
let mut env = SimEnv::with_seed(0);
let res = PreemptiveResource::new(1);
let log = new_log();
{
let h = env.handle();
let r = res.clone();
env.spawn(async move {
let _g = r.request(0).await;
h.timeout(5.0).await;
});
}
{
let h = env.handle();
let r = res.clone();
let log2 = log.clone();
env.spawn(async move {
h.timeout(1.0).await;
let _g = r.request(0).await;
log2.borrow_mut().push(format!("W:{}", h.now()));
h.timeout(2.0).await;
});
}
{
let h = env.handle();
let r = res.clone();
let log2 = log.clone();
env.spawn(async move {
h.timeout(5.0).await;
let _g = r.request(0).await;
log2.borrow_mut().push(format!("R:{}", h.now()));
h.timeout(2.0).await;
});
}
env.run();
let log = log.borrow();
assert!(log.iter().any(|l| l.starts_with("W:")), "W starved: {log:?}");
assert!(log.iter().any(|l| l.starts_with("R:")), "R starved: {log:?}");
assert_eq!(res.in_use(), 0);
}