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}