Skip to main content

simu/resource/
preemptive.rs

1// SPDX-FileCopyrightText: Copyright (c) Siemens 2026 contributed by Christoph Kuhmuench christoph.kuhmuench@gmail.com
2//
3// SPDX-License-Identifier: MIT OR Apache-2.0
4
5//! `PreemptiveResource` — a priority resource whose in-use units can be
6//! *preempted* by a higher-priority request.
7//!
8//! ## Cooperative-at-yield semantics
9//!
10//! A discrete-event executor cannot forcibly unwind a process that is suspended
11//! on an unrelated future (e.g. a service `timeout`): the only lever it has is
12//! the process's `Waker`, and waking it merely re-polls the same `Pending`
13//! future. Truly preemptive cancellation would require unwinding arbitrary
14//! suspended stacks, which Rust's async model does not permit from the outside.
15//!
16//! `PreemptiveResource` therefore delivers preemption the way SimPy delivers an
17//! interrupt and the way all Rust async cancellation works: **at the victim's
18//! next yield point.** When a higher-priority request preempts a holder, the
19//! holder's unit is transferred away immediately *and* its preemption signal is
20//! fired. A well-behaved victim races its work against that signal — see the
21//! [`PreemptiveResource`] type docs for the full pattern.
22//!
23//! A victim that ignores its signal keeps running (it has already lost the unit
24//! on the books, so it can no longer block anyone). This mirrors the fact that
25//! a Rust future which never checks for cancellation simply runs to completion.
26
27use std::cell::{Cell, RefCell};
28use std::future::Future;
29use std::pin::Pin;
30use std::rc::Rc;
31use std::task::{Context, Poll};
32
33use crate::event::{new_event, EventAwaitable, EventTrigger};
34
35use super::wait_queue::WaitQueue;
36
37/// Bookkeeping for one unit that is currently held.
38struct Holder {
39    /// Priority the unit was acquired at (lower = higher priority). Used to
40    /// pick a victim: a request preempts the lowest-priority holder whose
41    /// priority is strictly worse (greater) than the request's.
42    priority: u32,
43    /// Unique id so a guard can find and remove exactly its own holder entry
44    /// (priorities are not unique).
45    id: u64,
46    /// Fired when this holder is preempted. Shared with the guard's
47    /// `EventAwaitable` so the victim can observe preemption at a yield point.
48    trigger: Option<EventTrigger>,
49    /// Mirrors `trigger`'s fired state for synchronous `is_preempted()` checks
50    /// without consuming the awaitable.
51    preempted: Rc<Cell<bool>>,
52}
53
54struct PreemptiveState {
55    /// Capacity accounting + the priority-ordered queue of *blocked* requests.
56    /// `in_use` here counts granted units; `holders.len()` mirrors it exactly.
57    wq: WaitQueue<u32>,
58    /// One entry per currently-held unit.
59    holders: Vec<Holder>,
60    /// Monotonic id source for holder entries.
61    next_holder_id: u64,
62}
63
64impl PreemptiveState {
65    /// Pick the holder to evict for an `incoming` request, or `None` if none
66    /// can be preempted.
67    ///
68    /// The victim is the holder with the **lowest priority** (numerically
69    /// greatest) whose priority is *strictly worse* than `incoming`. When
70    /// several holders share that lowest priority, the **most recently
71    /// acquired** one is chosen — it has made the least progress, so preempting
72    /// it wastes the least work. `max_by_key` returns the last maximal element,
73    /// and `holders` is kept in acquisition order, so this tie-break is
74    /// deterministic.
75    fn victim_index(&self, incoming: u32) -> Option<usize> {
76        self.holders
77            .iter()
78            .enumerate()
79            .filter(|(_, h)| h.priority > incoming)
80            .max_by_key(|(_, h)| h.priority)
81            .map(|(i, _)| i)
82    }
83}
84
85/// A cloneable handle to a capacity-limited pool whose held units can be
86/// **preempted** by higher-priority requests.
87///
88/// Like [`PriorityResource`](crate::PriorityResource), units are requested at a
89/// priority (lower number = higher priority) and blocked waiters are served in
90/// priority order. *Unlike* it, when every unit is in use a higher-priority
91/// request does not wait behind the holders — it **evicts** the lowest-priority
92/// holder whose priority is strictly worse than its own, taking that unit
93/// immediately.
94///
95/// Preemption is cooperative-at-yield: a higher-priority request fires the
96/// victim's [`preempted`](PreemptiveGuard::preempted) signal, and the victim is
97/// expected to bail via `any_of![work, guard.preempted()]`. A well-behaved
98/// holder races its work against that signal:
99///
100/// ```
101/// use simu::{SimEnv, PreemptiveResource, any_of};
102/// let mut env = SimEnv::with_seed(0);
103/// let res = PreemptiveResource::new(1);
104/// let h = env.handle();
105/// let r = res.clone();
106/// env.spawn(async move {
107///     let guard = r.request(2).await;
108///     let service = 10.0;
109///     // Race the service time against a possible preemption.
110///     any_of![h.timeout(service), guard.preempted()].await;
111///     if guard.is_preempted() {
112///         // Higher-priority work took the unit — abandon and clean up.
113///         return;
114///     }
115///     // Completed normally; dropping the guard releases the unit.
116/// });
117/// env.run();
118/// ```
119///
120/// `PreemptiveResource` wraps an `Rc<RefCell<>>` internally, so cloning is
121/// cheap and all clones share the same pool. It is `!Send + !Sync`.
122#[derive(Clone)]
123pub struct PreemptiveResource {
124    state: Rc<RefCell<PreemptiveState>>,
125}
126
127impl std::fmt::Debug for PreemptiveResource {
128    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129        let mut d = f.debug_struct("PreemptiveResource");
130        if let Ok(s) = self.state.try_borrow() {
131            d.field("in_use", &s.wq.in_use())
132                .field("capacity", &s.wq.capacity())
133                .field("queue_len", &s.wq.live_waiters());
134        }
135        d.finish_non_exhaustive()
136    }
137}
138
139impl PreemptiveResource {
140    /// Create a new preemptive resource pool with the given capacity.
141    ///
142    /// # Panics
143    ///
144    /// Panics if `capacity` is zero.
145    #[must_use]
146    pub fn new(capacity: usize) -> Self {
147        assert!(
148            capacity > 0,
149            "PreemptiveResource capacity must be at least 1"
150        );
151        PreemptiveResource {
152            state: Rc::new(RefCell::new(PreemptiveState {
153                wq: WaitQueue::new(capacity),
154                holders: Vec::new(),
155                next_holder_id: 0,
156            })),
157        }
158    }
159
160    /// Request one unit at the given `priority` (lower = higher priority).
161    ///
162    /// Resolves immediately if a unit is free **or** if a strictly
163    /// lower-priority holder can be preempted; otherwise suspends in priority
164    /// order until a unit is released or becomes preemptible.
165    ///
166    /// The returned [`PreemptiveGuard`] releases the unit when dropped, and
167    /// exposes [`preempted`](PreemptiveGuard::preempted) /
168    /// [`is_preempted`](PreemptiveGuard::is_preempted) so the holder can yield
169    /// the unit cooperatively.
170    #[must_use = "futures do nothing unless awaited"]
171    pub fn request(&self, priority: u32) -> PreemptiveRequest {
172        PreemptiveRequest {
173            state: Rc::clone(&self.state),
174            priority,
175            registered: false,
176            consumed: false,
177            canceled: Rc::new(Cell::new(false)),
178            granted: Rc::new(Cell::new(false)),
179        }
180    }
181
182    /// Number of units currently in use.
183    #[must_use]
184    pub fn in_use(&self) -> usize {
185        self.state.borrow().wq.in_use()
186    }
187
188    /// Total capacity of this resource pool.
189    #[must_use]
190    pub fn capacity(&self) -> usize {
191        self.state.borrow().wq.capacity()
192    }
193
194    /// Number of processes currently *blocked* waiting for a unit (i.e. those
195    /// that could neither take a free unit nor preempt a holder). Excludes
196    /// abandoned (canceled) requests and current holders.
197    #[must_use]
198    pub fn queue_len(&self) -> usize {
199        self.state.borrow().wq.live_waiters()
200    }
201}
202
203/// Build a guard for a freshly granted unit, registering its holder entry.
204/// Returns the guard; called from both the free-unit and preemption paths.
205fn grant(state_rc: &Rc<RefCell<PreemptiveState>>, priority: u32) -> PreemptiveGuard {
206    let (trigger, awaitable) = new_event();
207    let preempted = Rc::new(Cell::new(false));
208    let id = {
209        let mut state = state_rc.borrow_mut();
210        let id = state.next_holder_id;
211        state.next_holder_id += 1;
212        state.holders.push(Holder {
213            priority,
214            id,
215            trigger: Some(trigger),
216            preempted: Rc::clone(&preempted),
217        });
218        id
219    };
220    PreemptiveGuard {
221        state: Rc::clone(state_rc),
222        id,
223        signal: awaitable,
224        preempted,
225    }
226}
227
228/// Future returned by [`PreemptiveResource::request`].
229///
230/// Resolves to a [`PreemptiveGuard`] once a unit is acquired — either a free
231/// one, or one taken from a preempted lower-priority holder.
232pub struct PreemptiveRequest {
233    state: Rc<RefCell<PreemptiveState>>,
234    priority: u32,
235    registered: bool,
236    /// Set once a granted/acquired unit has become a holder+guard; `Drop` then
237    /// must not release (the guard owns the unit).
238    consumed: bool,
239    canceled: Rc<Cell<bool>>,
240    /// Shared with the queue entry; set to `true` by `WaitQueue::release` when a
241    /// released unit is handed directly to this blocked request. Checked first
242    /// in `poll`, exactly like the plain `Resource` handoff.
243    granted: Rc<Cell<bool>>,
244}
245
246impl std::fmt::Debug for PreemptiveRequest {
247    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
248        f.debug_struct("PreemptiveRequest")
249            .field("priority", &self.priority)
250            .field("registered", &self.registered)
251            .field("granted", &self.granted.get())
252            .finish_non_exhaustive()
253    }
254}
255
256impl Future for PreemptiveRequest {
257    type Output = PreemptiveGuard;
258
259    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<PreemptiveGuard> {
260        // Direct handoff: a released unit was transferred to us by `wq.release`.
261        // `in_use` already accounts for it and the releasing guard removed its
262        // holder entry, so we simply register our own holder via grant().
263        if self.granted.get() {
264            self.consumed = true;
265            return Poll::Ready(grant(&self.state, self.priority));
266        }
267
268        // Decide the outcome while holding the borrow, but build the guard
269        // afterwards (grant() re-borrows state).
270        enum Outcome {
271            Free,
272            Preempt(usize),
273            Block,
274        }
275
276        let outcome = {
277            let mut state = self.state.borrow_mut();
278            if state.wq.try_acquire() {
279                Outcome::Free
280            } else if let Some(idx) = state.victim_index(self.priority) {
281                Outcome::Preempt(idx)
282            } else {
283                if !self.registered {
284                    state.wq.register(
285                        self.priority,
286                        cx.waker().clone(),
287                        Rc::clone(&self.canceled),
288                        Rc::clone(&self.granted),
289                    );
290                }
291                Outcome::Block
292            }
293        };
294
295        match outcome {
296            Outcome::Free => {
297                self.consumed = true;
298                Poll::Ready(grant(&self.state, self.priority))
299            }
300            Outcome::Preempt(idx) => {
301                // Evict the victim: fire its signal and remove its holder entry.
302                // Capacity bookkeeping is unchanged — the unit transfers
303                // directly from victim to us without passing through the queue.
304                let victim = {
305                    let mut state = self.state.borrow_mut();
306                    state.holders.remove(idx)
307                };
308                victim.preempted.set(true);
309                if let Some(trigger) = victim.trigger {
310                    trigger.fire();
311                }
312                self.consumed = true;
313                Poll::Ready(grant(&self.state, self.priority))
314            }
315            Outcome::Block => {
316                self.registered = true;
317                Poll::Pending
318            }
319        }
320    }
321}
322
323impl Drop for PreemptiveRequest {
324    fn drop(&mut self) {
325        if self.consumed {
326            return; // the guard owns the unit and will release it
327        }
328        if self.granted.get() {
329            // A unit was handed to us but never turned into a holder (dropped
330            // before re-poll). No holder was created yet, so just release it
331            // back into the queue to hand it on to the next waiter.
332            self.state.borrow_mut().wq.release();
333        } else if self.registered {
334            self.canceled.set(true);
335        }
336    }
337}
338
339/// RAII guard holding one unit of a [`PreemptiveResource`].
340///
341/// Dropping the guard releases the unit (waking the next waiter) **unless** the
342/// unit was already preempted, in which case the drop is a no-op — the unit has
343/// already been handed to the preemptor.
344pub struct PreemptiveGuard {
345    state: Rc<RefCell<PreemptiveState>>,
346    id: u64,
347    signal: EventAwaitable,
348    preempted: Rc<Cell<bool>>,
349}
350
351impl std::fmt::Debug for PreemptiveGuard {
352    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
353        f.debug_struct("PreemptiveGuard")
354            .field("id", &self.id)
355            .field("preempted", &self.preempted.get())
356            .finish_non_exhaustive()
357    }
358}
359
360impl PreemptiveGuard {
361    /// A future that resolves when this unit is preempted by a higher-priority
362    /// request. Intended for racing against the holder's work, e.g.
363    /// `any_of![env.timeout(d), guard.preempted()]`.
364    ///
365    /// Resolves immediately if preemption has already happened.
366    #[must_use = "futures do nothing unless awaited"]
367    pub fn preempted(&self) -> EventAwaitable {
368        self.signal.clone()
369    }
370
371    /// Synchronously report whether this unit has been preempted. Check this
372    /// after a race to decide whether to abandon the work.
373    #[must_use]
374    pub fn is_preempted(&self) -> bool {
375        self.preempted.get()
376    }
377}
378
379impl Drop for PreemptiveGuard {
380    fn drop(&mut self) {
381        let mut state = self.state.borrow_mut();
382        // If preempted, the holder entry is already gone and the unit was
383        // transferred to the preemptor — releasing again would double-count.
384        if self.preempted.get() {
385            return;
386        }
387        if let Some(pos) = state.holders.iter().position(|h| h.id == self.id) {
388            state.holders.remove(pos);
389        }
390        state.wq.release();
391    }
392}