umsh_sync/async_refcell.rs
1//! `AsyncRefCell<T>` — an async-aware `RefCell`.
2//!
3//! `borrow()` / `borrow_mut()` return futures that wait until the cell is
4//! available instead of panicking. Built on top of [`AsyncCondition`]: when
5//! the last outstanding guard is dropped, all waiting borrowers are woken
6//! and race to re-probe the underlying `RefCell`. Losers re-queue.
7//!
8//! This is a single-threaded primitive — it holds a `RefCell` internally
9//! and is `!Sync`. For cross-thread sharing, use a proper mutex crate.
10
11use core::cell::{self, RefCell};
12use core::fmt;
13use core::ops::{Deref, DerefMut};
14use core::task::{Context, Poll};
15
16use crate::{AsyncCondition, AsyncConditionTicket};
17
18/// Single-threaded async-aware interior-mutability cell.
19///
20/// Mirrors `core::cell::RefCell`, but `borrow()` and `borrow_mut()` are
21/// futures that await until the cell is available rather than panicking
22/// on contention.
23pub struct AsyncRefCell<T: ?Sized> {
24 cond: AsyncCondition,
25 inner: RefCell<T>,
26}
27
28impl<T> AsyncRefCell<T> {
29 /// Construct a new cell holding `value`.
30 pub fn new(value: T) -> Self {
31 Self {
32 cond: AsyncCondition::new(),
33 inner: RefCell::new(value),
34 }
35 }
36
37 /// Consume the cell and return the contained value.
38 pub fn into_inner(self) -> T {
39 self.inner.into_inner()
40 }
41}
42
43impl<T: ?Sized> AsyncRefCell<T> {
44 /// Wait until an immutable borrow is available, then take it.
45 ///
46 /// Multiple `borrow()` guards may be held concurrently. `borrow_mut()`
47 /// waits until all of them have been dropped.
48 pub async fn borrow(&self) -> Ref<'_, T> {
49 loop {
50 let wait = self.cond.wait();
51 if let Ok(inner) = self.inner.try_borrow() {
52 return Ref {
53 inner,
54 cond: &self.cond,
55 };
56 }
57 wait.await;
58 }
59 }
60
61 /// Wait until an exclusive borrow is available, then take it.
62 pub async fn borrow_mut(&self) -> RefMut<'_, T> {
63 loop {
64 let wait = self.cond.wait();
65 if let Ok(inner) = self.inner.try_borrow_mut() {
66 return RefMut {
67 inner,
68 cond: &self.cond,
69 };
70 }
71 wait.await;
72 }
73 }
74
75 /// Attempt to take an immutable borrow without waiting.
76 pub fn try_borrow(&self) -> Option<Ref<'_, T>> {
77 self.inner.try_borrow().ok().map(|inner| Ref {
78 inner,
79 cond: &self.cond,
80 })
81 }
82
83 /// Attempt to take an exclusive borrow without waiting.
84 pub fn try_borrow_mut(&self) -> Option<RefMut<'_, T>> {
85 self.inner.try_borrow_mut().ok().map(|inner| RefMut {
86 inner,
87 cond: &self.cond,
88 })
89 }
90
91 /// Poll `f` with a short-lived exclusive borrow, staying registered on the
92 /// cell's wake condition across `Pending` polls.
93 ///
94 /// This is the primitive for `poll_fn`-style drivers that need to race a
95 /// borrow attempt against other wake sources (radio I/O, a timer) *and* be
96 /// re-polled whenever a guard is released — for example when a second
97 /// handle mutates the cell and drops its borrow.
98 ///
99 /// Behavior per poll:
100 ///
101 /// 1. Deregister this task's waker from the wake condition. Every guard
102 /// drop triggers the condition, so a waker registered *before* taking
103 /// the borrow would be woken by our **own** guard drop in step 2,
104 /// re-polling the task in a busy loop. This crate is single-threaded,
105 /// so nothing can trigger between this step and step 3 — no wakeup can
106 /// be lost to the gap.
107 /// 2. If the cell is free, take the exclusive borrow and run `f` (which
108 /// may register other wakers, e.g. radio or timer). The guard is
109 /// dropped — waking *other* waiters — before step 3.
110 /// 3. If `f` returned `Pending` (or the cell was busy), re-register on the
111 /// condition so a later guard release re-polls this task.
112 ///
113 /// `ticket` must be obtained from [`scoped_ticket`](Self::scoped_ticket)
114 /// once before the wait begins and reused across every poll of that wait.
115 /// The ticket deregisters itself on drop, so a wait that is cancelled
116 /// mid-`Pending` (e.g. losing a `select!` race) cannot leak its waker
117 /// registration.
118 pub fn poll_with_mut<R>(
119 &self,
120 cx: &mut Context<'_>,
121 ticket: &mut ScopedTicket<'_>,
122 f: impl FnOnce(&mut T, &mut Context<'_>) -> Poll<R>,
123 ) -> Poll<R> {
124 debug_assert!(
125 core::ptr::eq(ticket.cond, &self.cond),
126 "ScopedTicket used with a different AsyncRefCell than it was created from"
127 );
128 ticket.cond.forget_ticket(&mut ticket.ticket);
129 let result = match self.try_borrow_mut() {
130 // The guard drops at the end of this arm — while this task is
131 // deregistered — so our own release never wakes us.
132 Some(mut guard) => f(&mut guard, cx),
133 None => Poll::Pending,
134 };
135 if result.is_pending() {
136 ticket.ticket = ticket.cond.ticket();
137 let _ = ticket.cond.poll_wait(cx, &mut ticket.ticket);
138 }
139 result
140 }
141
142 /// Create an RAII ticket for use with [`poll_with_mut`](Self::poll_with_mut).
143 ///
144 /// The ticket deregisters from the cell's wake condition when dropped, so
145 /// waits abandoned before completion do not leak waker slots.
146 pub fn scoped_ticket(&self) -> ScopedTicket<'_> {
147 ScopedTicket {
148 cond: &self.cond,
149 ticket: self.cond.ticket(),
150 }
151 }
152
153 /// Borrow the underlying wake condition.
154 ///
155 /// Each guard drop triggers this condition, waking any task currently
156 /// suspended on `cond.wait()`. Useful for callers that want to race a
157 /// borrow attempt against other wake sources (e.g. a `poll_fn` that
158 /// should re-poll when the cell becomes available *or* when a timer
159 /// fires, whichever comes first).
160 pub fn cond(&self) -> &AsyncCondition {
161 &self.cond
162 }
163}
164
165impl<T: Default> Default for AsyncRefCell<T> {
166 fn default() -> Self {
167 Self::new(T::default())
168 }
169}
170
171impl<T: fmt::Debug> fmt::Debug for AsyncRefCell<T> {
172 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173 f.debug_struct("AsyncRefCell")
174 .field("inner", &self.inner)
175 .finish()
176 }
177}
178
179/// RAII condition ticket vended by [`AsyncRefCell::scoped_ticket`].
180///
181/// Deregisters its waker slot from the cell's wake condition on drop, so a
182/// wait that is cancelled mid-flight (a dropped `poll_fn` future) cannot leak
183/// its registration.
184pub struct ScopedTicket<'a> {
185 cond: &'a AsyncCondition,
186 ticket: AsyncConditionTicket,
187}
188
189impl Drop for ScopedTicket<'_> {
190 fn drop(&mut self) {
191 self.cond.forget_ticket(&mut self.ticket);
192 }
193}
194
195/// Shared immutable guard returned by [`AsyncRefCell::borrow`].
196pub struct Ref<'a, T: ?Sized> {
197 inner: cell::Ref<'a, T>,
198 cond: &'a AsyncCondition,
199}
200
201impl<T: ?Sized> Deref for Ref<'_, T> {
202 type Target = T;
203 fn deref(&self) -> &T {
204 &self.inner
205 }
206}
207
208impl<T: ?Sized> Drop for Ref<'_, T> {
209 fn drop(&mut self) {
210 self.cond.trigger();
211 }
212}
213
214/// Exclusive mutable guard returned by [`AsyncRefCell::borrow_mut`].
215pub struct RefMut<'a, T: ?Sized> {
216 inner: cell::RefMut<'a, T>,
217 cond: &'a AsyncCondition,
218}
219
220impl<T: ?Sized> Deref for RefMut<'_, T> {
221 type Target = T;
222 fn deref(&self) -> &T {
223 &self.inner
224 }
225}
226
227impl<T: ?Sized> DerefMut for RefMut<'_, T> {
228 fn deref_mut(&mut self) -> &mut T {
229 &mut self.inner
230 }
231}
232
233impl<T: ?Sized> Drop for RefMut<'_, T> {
234 fn drop(&mut self) {
235 self.cond.trigger();
236 }
237}
238
239#[cfg(test)]
240mod tests {
241 use super::*;
242 use alloc::boxed::Box;
243 use futures::FutureExt;
244 use futures::executor::block_on;
245
246 /// Uncontended: `borrow()` and `borrow_mut()` resolve immediately.
247 #[test]
248 fn uncontended_borrows_resolve_immediately() {
249 let cell = AsyncRefCell::new(42u32);
250 let r = cell.borrow().now_or_never().expect("borrow should resolve");
251 assert_eq!(*r, 42);
252 drop(r);
253 let mut w = cell
254 .borrow_mut()
255 .now_or_never()
256 .expect("borrow_mut should resolve");
257 *w = 7;
258 drop(w);
259 assert_eq!(*cell.borrow().now_or_never().unwrap(), 7);
260 }
261
262 /// Multiple `borrow()`s coexist; `borrow_mut()` waits for them.
263 #[test]
264 fn shared_borrows_block_exclusive() {
265 let cell = AsyncRefCell::new(1u32);
266 let r1 = cell.borrow().now_or_never().unwrap();
267 let r2 = cell.borrow().now_or_never().unwrap();
268 // borrow_mut cannot yet succeed
269 let mut fut = Box::pin(cell.borrow_mut());
270 assert!((&mut fut).now_or_never().is_none());
271 drop(r1);
272 // one reader remains; still blocked
273 assert!((&mut fut).now_or_never().is_none());
274 drop(r2);
275 // now it should succeed on next poll
276 let w = block_on(fut);
277 assert_eq!(*w, 1);
278 }
279
280 /// Dropping a `borrow_mut()` guard wakes a waiting `borrow_mut()`.
281 #[test]
282 fn exclusive_drop_wakes_waiter() {
283 block_on(async {
284 let cell = AsyncRefCell::new(0u32);
285 let w = cell.borrow_mut().await;
286 // Second borrow_mut starts waiting.
287 let mut fut = Box::pin(cell.borrow_mut());
288 assert!((&mut fut).now_or_never().is_none());
289 drop(w);
290 let mut w2 = fut.await;
291 *w2 = 99;
292 drop(w2);
293 assert_eq!(*cell.borrow().await, 99);
294 });
295 }
296
297 /// Dropping a pending `borrow_mut()` future leaves the cell usable.
298 #[test]
299 fn cancelled_wait_does_not_leak() {
300 let cell = AsyncRefCell::new(0u32);
301 let w = cell.borrow_mut().now_or_never().unwrap();
302 {
303 // Pending waiter created and dropped before it ever resolves.
304 let mut fut = Box::pin(cell.borrow_mut());
305 assert!((&mut fut).now_or_never().is_none());
306 drop(fut);
307 }
308 drop(w);
309 // Cell is still usable: new borrow_mut resolves immediately.
310 let w2 = cell
311 .borrow_mut()
312 .now_or_never()
313 .expect("cell should be free");
314 drop(w2);
315 }
316
317 /// A waker that counts how many times it is woken.
318 struct WakeCounter(core::sync::atomic::AtomicUsize);
319
320 impl futures::task::ArcWake for WakeCounter {
321 fn wake_by_ref(arc_self: &alloc::sync::Arc<Self>) {
322 arc_self
323 .0
324 .fetch_add(1, core::sync::atomic::Ordering::SeqCst);
325 }
326 }
327
328 impl WakeCounter {
329 fn count(&self) -> usize {
330 self.0.load(core::sync::atomic::Ordering::SeqCst)
331 }
332 }
333
334 fn counting_waker() -> (core::task::Waker, alloc::sync::Arc<WakeCounter>) {
335 let counter = alloc::sync::Arc::new(WakeCounter(core::sync::atomic::AtomicUsize::new(0)));
336 let waker = futures::task::waker(counter.clone());
337 (waker, counter)
338 }
339
340 /// `poll_with_mut` runs the closure when the cell is free, is `Pending`
341 /// while a guard is held, and wakes when that guard is released.
342 #[test]
343 fn poll_with_mut_waits_for_release() {
344 use core::future::poll_fn;
345 use core::task::Poll;
346
347 let cell = AsyncRefCell::new(5u32);
348 let mut ticket = cell.scoped_ticket();
349 let guard = cell.borrow_mut().now_or_never().unwrap();
350
351 let mut fut = Box::pin(poll_fn(|cx| {
352 cell.poll_with_mut(cx, &mut ticket, |value, _cx| Poll::Ready(*value))
353 }));
354 // Held exclusively elsewhere: not ready.
355 assert!((&mut fut).now_or_never().is_none());
356 drop(guard);
357 // Released: the condition wake re-polls and the closure runs.
358 assert_eq!(fut.now_or_never(), Some(5));
359 }
360
361 /// Regression test: a `Pending` poll of `poll_with_mut` must not be woken
362 /// by its **own** guard drop. The pre-`poll_with_mut` pattern registered
363 /// on the condition before taking the borrow, so the guard drop at the end
364 /// of each poll re-woke the task — a permanent executor spin.
365 #[test]
366 fn poll_with_mut_own_guard_drop_does_not_self_wake() {
367 use core::task::{Context, Poll};
368
369 let (waker, wakes) = counting_waker();
370 let mut cx = Context::from_waker(&waker);
371
372 let cell = AsyncRefCell::new(0u32);
373 let mut ticket = cell.scoped_ticket();
374
375 // Borrow succeeds, closure returns Pending (like a radio with no
376 // frame ready), guard drops inside the call.
377 let result: Poll<()> =
378 cell.poll_with_mut(&mut cx, &mut ticket, |_value, _cx| Poll::Pending);
379 assert!(result.is_pending());
380 assert_eq!(
381 wakes.count(),
382 0,
383 "own guard drop must not wake the polling task"
384 );
385
386 // But another holder's release *does* wake us.
387 drop(cell.borrow_mut().now_or_never().unwrap());
388 assert_eq!(wakes.count(), 1);
389 }
390
391 /// Regression test: dropping a `ScopedTicket` mid-`Pending` (a wait
392 /// cancelled by losing a `select!` race) deregisters its waker slot, so
393 /// repeated cancelled waits do not leak slab entries or receive wakes.
394 #[test]
395 fn scoped_ticket_drop_deregisters() {
396 use core::task::{Context, Poll};
397
398 let (waker, wakes) = counting_waker();
399 let mut cx = Context::from_waker(&waker);
400
401 let cell = AsyncRefCell::new(0u32);
402 {
403 let mut ticket = cell.scoped_ticket();
404 let result: Poll<()> =
405 cell.poll_with_mut(&mut cx, &mut ticket, |_value, _cx| Poll::Pending);
406 assert!(result.is_pending());
407 // Wait cancelled here: ticket dropped while registered.
408 }
409 // A later guard release must not wake the abandoned waiter.
410 drop(cell.borrow_mut().now_or_never().unwrap());
411 assert_eq!(wakes.count(), 0, "cancelled wait must deregister its waker");
412 }
413
414 /// Race: holder releases between a waiter's `wait()` registration and
415 /// its `try_borrow_mut()` probe. The probe succeeds — the waiter never
416 /// actually `.await`s. Verifies the "register-first, probe-second"
417 /// ordering closes the lost-wakeup window.
418 #[test]
419 fn register_first_probe_second_closes_race() {
420 block_on(async {
421 let cell = AsyncRefCell::new(0u32);
422 // No holder exists, so borrow_mut resolves in one poll.
423 let w = cell.borrow_mut().await;
424 drop(w);
425 });
426 }
427}