Skip to main content

access_cell/
lib.rs

1//! A cell that allows mutable access with re-entrancy support.
2//!
3//! When you call [`access`](AccessCell::access) with a closure that itself calls `access`
4//! on the same cell, those nested calls are queued and run after the current closure
5//! finishes, avoiding deadlock.
6
7use std::{
8    cell::{Cell, UnsafeCell},
9    collections::VecDeque,
10    sync::Mutex,
11};
12
13/// A cell holding a value of type `T` with re-entrant mutable access.
14///
15/// The first caller of [`access`](AccessCell::access) runs immediately; any further
16/// calls to `access` from within that closure (or its queue) are enqueued and
17/// executed in order after the current work completes.
18pub struct AccessCell<T> {
19    value: UnsafeCell<T>,
20    running: Cell<bool>,
21    queue: Mutex<VecDeque<Box<dyn FnOnce(&mut T)>>>,
22}
23
24impl<T> AccessCell<T> {
25    /// Creates a new `AccessCell` wrapping `value`.
26    pub fn new(value: T) -> Self {
27        Self {
28            value: UnsafeCell::new(value),
29            running: Cell::new(false),
30            queue: Mutex::new(VecDeque::new()),
31        }
32    }
33
34    /// Runs `f` with exclusive mutable access to the inner value.
35    ///
36    /// If called while another `access` closure is running (re-entrant call),
37    /// `f` is queued and run after the current closure and any other queued
38    /// closures finish.
39    pub fn access(&self, f: impl FnOnce(&mut T) + 'static) {
40        // already inside → enqueue
41        if self.running.get() {
42            self.queue.lock().unwrap().push_back(Box::new(f));
43
44            return;
45        }
46
47        // first entrant = executor
48        self.running.set(true);
49
50        // call current
51        f(self.access_mut());
52
53        // drain queued re-entrant calls
54        while let Some(job) = self.queue.lock().unwrap().pop_front() {
55            let value = self.access_mut();
56            job(value);
57        }
58
59        self.running.set(false);
60    }
61
62    /// Returns a mutable reference to the inner value.
63    ///
64    /// # Safety
65    /// Only call this while you are inside an `access` closure (or while draining
66    /// the queue). Otherwise you may create aliasing mutable references.
67    pub fn access_mut(&self) -> &mut T {
68        unsafe { &mut *self.value.get() }
69    }
70
71    /// Returns an immutable reference to the inner value.
72    ///
73    /// Safe to call at any time; no closure is running when you use this for
74    /// read-only access.
75    pub fn access_ref(&self) -> &T {
76        unsafe { &*self.value.get() }
77    }
78}
79
80mod tests {
81    #[test]
82    fn test_access() {
83        let value = std::sync::Arc::new(crate::AccessCell::new(0));
84
85        value.access({
86            let value = value.clone();
87
88            move |_| {
89                // normally this would cause a deadlock
90                value.access(|v| {
91                    *v = 10;
92                })
93            }
94        });
95
96        assert_eq!(*value.access_ref(), 10);
97    }
98}