simu/resource/mod.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
5use std::cell::{Cell, RefCell};
6use std::future::Future;
7use std::pin::Pin;
8use std::rc::Rc;
9use std::task::{Context, Poll};
10
11pub mod container;
12pub use container::{Container, ContainerGetRequest, ContainerPutRequest};
13
14mod preemptive;
15pub use preemptive::{PreemptiveGuard, PreemptiveRequest, PreemptiveResource};
16pub mod priority;
17pub use priority::{PriorityResource, PriorityResourceGuard, PriorityResourceRequest};
18
19pub(crate) mod wait_queue;
20use wait_queue::WaitQueue;
21
22/// A cloneable handle to a capacity-limited resource pool.
23///
24/// Units are acquired by calling [`request`](Resource::request) and awaiting
25/// the returned future. If no unit is available the calling process is
26/// suspended and woken in FIFO order when one becomes free.
27///
28/// `Resource` wraps an `Rc<RefCell<>>` internally, so cloning is cheap and
29/// all clones share the same pool. It is `!Send + !Sync` — consistent with
30/// `SimEnv`.
31///
32/// Two processes sharing a single-unit pump — the second queues until the
33/// first one's guard drops:
34///
35/// ```
36/// use simu::{SimEnv, Resource};
37///
38/// let mut env = SimEnv::with_seed(0);
39/// let pump = Resource::new(1);
40///
41/// for _ in 0..2 {
42/// let h = env.handle();
43/// let p = pump.clone(); // same pool — share by cloning, never Arc
44/// env.spawn(async move {
45/// let _guard = p.request().await; // second process suspends here
46/// h.timeout(3.0).await; // use the pump for 3 time units
47/// }); // guard drops → pump handed to the next waiter
48/// }
49///
50/// env.run();
51/// assert_eq!(env.now(), 6.0); // servings ran back-to-back, not in parallel
52/// ```
53///
54/// FIFO ordering is the degenerate `WaitQueue<()>` case: every waiter shares
55/// the same (unit) key, so they are served purely in insertion order.
56#[derive(Clone)]
57pub struct Resource {
58 state: Rc<RefCell<WaitQueue<()>>>,
59}
60
61impl std::fmt::Debug for Resource {
62 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63 let mut d = f.debug_struct("Resource");
64 if let Ok(q) = self.state.try_borrow() {
65 d.field("in_use", &q.in_use())
66 .field("capacity", &q.capacity())
67 .field("queue_len", &q.live_waiters());
68 }
69 d.finish_non_exhaustive()
70 }
71}
72
73impl Resource {
74 /// Create a new resource pool with the given capacity.
75 ///
76 /// # Panics
77 /// Panics if `capacity` is zero.
78 #[must_use]
79 pub fn new(capacity: usize) -> Self {
80 assert!(capacity > 0, "Resource capacity must be at least 1");
81 Resource {
82 state: Rc::new(RefCell::new(WaitQueue::new(capacity))),
83 }
84 }
85
86 /// Request one unit. Resolves immediately if a unit is available,
87 /// otherwise suspends the calling process until one is released.
88 ///
89 /// The returned [`ResourceGuard`] releases the unit when dropped.
90 #[must_use = "futures do nothing unless awaited"]
91 pub fn request(&self) -> ResourceRequest {
92 ResourceRequest {
93 state: Rc::clone(&self.state),
94 registered: false,
95 consumed: false,
96 canceled: Rc::new(Cell::new(false)),
97 granted: Rc::new(Cell::new(false)),
98 }
99 }
100
101 /// Number of units currently in use.
102 #[must_use]
103 pub fn in_use(&self) -> usize {
104 self.state.borrow().in_use()
105 }
106
107 /// Total capacity of this resource pool.
108 #[must_use]
109 pub fn capacity(&self) -> usize {
110 self.state.borrow().capacity()
111 }
112
113 /// Number of processes currently queued waiting for a unit (SimPy's
114 /// `len(resource.queue)`). Excludes abandoned (canceled) requests.
115 #[must_use]
116 pub fn queue_len(&self) -> usize {
117 self.state.borrow().live_waiters()
118 }
119}
120
121/// Future returned by [`Resource::request`].
122///
123/// Resolves to a [`ResourceGuard`] once a unit is acquired.
124pub struct ResourceRequest {
125 state: Rc<RefCell<WaitQueue<()>>>,
126 /// Whether this request has already been enqueued in the wait queue.
127 /// Prevents double-queuing on repeated polls.
128 registered: bool,
129 /// Set once this request has turned a granted/acquired unit into a
130 /// `ResourceGuard`. Once consumed, `Drop` must not release: the guard owns
131 /// the unit and will release it itself.
132 consumed: bool,
133 /// Shared with the queue entry; set to `true` on drop if the request was
134 /// registered but never granted, so the release loop skips it.
135 canceled: Rc<Cell<bool>>,
136 /// Shared with the queue entry; set to `true` by `WaitQueue::release` when
137 /// the unit is handed directly to this request. Checked first in `poll`.
138 granted: Rc<Cell<bool>>,
139}
140
141impl std::fmt::Debug for ResourceRequest {
142 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143 f.debug_struct("ResourceRequest")
144 .field("registered", &self.registered)
145 .field("granted", &self.granted.get())
146 .finish_non_exhaustive()
147 }
148}
149
150impl Future for ResourceRequest {
151 type Output = ResourceGuard;
152
153 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<ResourceGuard> {
154 // Direct handoff: a released unit was transferred to us. Take it without
155 // touching capacity — `in_use` already accounts for this unit.
156 if self.granted.get() {
157 self.consumed = true;
158 return Poll::Ready(ResourceGuard {
159 state: Rc::clone(&self.state),
160 });
161 }
162 // Drop the borrow before writing self.registered to satisfy the borrow checker.
163 let acquired = {
164 let mut state = self.state.borrow_mut();
165 if state.try_acquire() {
166 true
167 } else {
168 if !self.registered {
169 state.register(
170 (),
171 cx.waker().clone(),
172 Rc::clone(&self.canceled),
173 Rc::clone(&self.granted),
174 );
175 }
176 false
177 }
178 };
179 if acquired {
180 self.consumed = true;
181 return Poll::Ready(ResourceGuard {
182 state: Rc::clone(&self.state),
183 });
184 }
185 self.registered = true;
186 Poll::Pending
187 }
188}
189
190impl Drop for ResourceRequest {
191 fn drop(&mut self) {
192 if self.consumed {
193 return; // the guard owns the unit and will release it
194 }
195 if self.granted.get() {
196 // A unit was handed to us but never turned into a guard (e.g. the
197 // future was dropped before its re-poll). Pass it straight on to
198 // the next waiter so it is not leaked.
199 self.state.borrow_mut().release();
200 } else if self.registered {
201 self.canceled.set(true);
202 }
203 }
204}
205
206/// RAII guard that holds one unit of a [`Resource`].
207///
208/// The unit is released automatically when this value is dropped, waking the
209/// next suspended requester (if any) in FIFO order.
210pub struct ResourceGuard {
211 state: Rc<RefCell<WaitQueue<()>>>,
212}
213
214impl std::fmt::Debug for ResourceGuard {
215 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
216 f.debug_struct("ResourceGuard").finish_non_exhaustive()
217 }
218}
219
220impl Drop for ResourceGuard {
221 fn drop(&mut self) {
222 // Release one unit and wake the next live (non-canceled) waiter; the
223 // WaitQueue skips abandoned entries so live waiters are not stranded.
224 self.state.borrow_mut().release();
225 }
226}