Skip to main content

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}