feat(channel): select_timeout — bounded select on a stateless stamped timer arm
The deadline is one more stamped waker on the select's wait epoch: a unit TimerTarget whose on_timeout is a bare unpark_at. Nothing registers in any arm for it, so there is nothing to cancel or leak — an arm winning leaves the timer entry to die at its epoch CAS; the timer winning leaves the arms' registrations to self-clean like any select loser's. Classification is pure channel state (wakes are precise): some arm ready -> Some(first, priority order); none -> the timer was the only remaining stamped waker -> None. Message-first on a raced deadline, same as recv_timeout. Registration pass factored into register_arms, which owns the retire_wait obligation on the ready-now exit for both entry points.
This commit is contained in:
@@ -243,3 +243,98 @@ fn motivating_pattern_inbox_plus_monitor_down() {
|
||||
worker.join().unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// select_timeout
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
use smarm::select_timeout;
|
||||
|
||||
#[test]
|
||||
fn select_timeout_returns_none_after_the_deadline() {
|
||||
run(|| {
|
||||
let (_keep_a, rxa) = channel::<i64>();
|
||||
let (_keep_b, rxb) = channel::<i64>();
|
||||
let t0 = Instant::now();
|
||||
let r = select_timeout(&[&rxa, &rxb], Duration::from_millis(30));
|
||||
assert_eq!(r, None);
|
||||
assert!(t0.elapsed() >= Duration::from_millis(30));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn select_timeout_ready_arm_wins_without_arming_a_timer() {
|
||||
run(|| {
|
||||
let (txa, rxa) = channel::<i64>();
|
||||
let (_keep_b, rxb) = channel::<i64>();
|
||||
txa.send(5).unwrap();
|
||||
assert_eq!(select_timeout(&[&rxb, &rxa], Duration::from_millis(500)), Some(1));
|
||||
assert_eq!(rxa.try_recv().unwrap(), Some(5));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn select_timeout_arm_beats_timer_and_stale_entry_stays_inert() {
|
||||
run(|| {
|
||||
let (txa, rxa) = channel::<i64>();
|
||||
let (_keep_b, rxb) = channel::<i64>();
|
||||
let h = spawn(move || {
|
||||
smarm::sleep(Duration::from_millis(5));
|
||||
txa.send(1).unwrap();
|
||||
});
|
||||
let t0 = Instant::now();
|
||||
let r = select_timeout(&[&rxa, &rxb], Duration::from_millis(200));
|
||||
assert_eq!(r, Some(0));
|
||||
assert!(t0.elapsed() < Duration::from_millis(200));
|
||||
assert_eq!(rxa.try_recv().unwrap(), Some(1));
|
||||
h.join().unwrap();
|
||||
// The abandoned timer entry expires mid-sleep; a stale-epoch wake
|
||||
// landing would return this one-shot park early.
|
||||
let t1 = Instant::now();
|
||||
smarm::sleep(Duration::from_millis(250));
|
||||
assert!(
|
||||
t1.elapsed() >= Duration::from_millis(250),
|
||||
"one-shot park returned early: the stale select timer landed"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn select_timeout_timer_first_message_still_delivered_later() {
|
||||
run(|| {
|
||||
let (txa, rxa) = channel::<i64>();
|
||||
let h = spawn(move || {
|
||||
smarm::sleep(Duration::from_millis(60));
|
||||
txa.send(9).unwrap();
|
||||
});
|
||||
assert_eq!(select_timeout(&[&rxa], Duration::from_millis(10)), None);
|
||||
// The wait is over but the channel is intact: the late message
|
||||
// arrives and a fresh select sees it.
|
||||
assert_eq!(select(&[&rxa]), 0);
|
||||
assert_eq!(rxa.try_recv().unwrap(), Some(9));
|
||||
h.join().unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn select_timeout_zero_duration_polls() {
|
||||
run(|| {
|
||||
let (txa, rxa) = channel::<i64>();
|
||||
let (_keep_b, rxb) = channel::<i64>();
|
||||
assert_eq!(select_timeout(&[&rxa, &rxb], Duration::ZERO), None);
|
||||
txa.send(3).unwrap();
|
||||
assert_eq!(select_timeout(&[&rxa, &rxb], Duration::ZERO), Some(0));
|
||||
assert_eq!(rxa.try_recv().unwrap(), Some(3));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn select_timeout_closed_arm_is_ready_not_a_timeout() {
|
||||
run(|| {
|
||||
let (_keep_a, rxa) = channel::<i64>();
|
||||
let (txb, rxb) = channel::<i64>();
|
||||
drop(txb);
|
||||
assert_eq!(select_timeout(&[&rxa, &rxb], Duration::from_millis(200)), Some(1));
|
||||
assert!(rxb.try_recv().is_err());
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user