Skip to main content

ibag/
cell.rs

1// Copyright 2023 Brian G
2// Licensed under the MIT license (https://opensource.org/licenses/MIT)
3
4#![allow(non_camel_case_types)]
5
6use std::cmp;
7use std::fmt;
8use std::mem;
9use std::sync::Mutex;
10use std::thread;
11use std::thread::ThreadId;
12use std::sync::Arc;
13
14use crate::errors::FailTakeOwnership;
15use crate::errors::InvalidThreadAccess;
16use std::mem::ManuallyDrop;
17
18/// A guard structure that tracks the ownership state of an iCell
19/// This is used internally by `iCell` to enforce thread confinement.
20///
21/// # Fields
22/// - `freeze`: Indicates if the cell is locked (ownership taken)
23/// - `thread_id`: The thread that currently owns the cell
24///
25/// # Safety
26/// The guard is protected by a `Mutex` to ensure thread-safe access.
27pub struct CellGuard {
28    pub freeze: bool,
29    pub thread_id: ThreadId,
30}
31
32/// A thread-confined cell that enforces single-thread access to its contents
33///
34/// This type ensures that its contents can only be accessed from the thread that
35/// currently owns it. Ownership can be transferred between threads using
36/// `take_ownership()`.
37///
38/// # Type Parameters
39/// - `T`: The type of value being stored in the cell
40///
41/// # Fields
42/// - `value`: The wrapped value, protected by thread ownership rules
43/// - `guard`: Shared state tracking the current owning thread
44///
45/// # Thread Safety
46/// While `iCell` implements both `Send` and `Sync`, direct access to the contained
47/// value is only permitted from the owning thread.
48pub struct iCell<T> {
49    value: ManuallyDrop<T>,
50    guard: Arc<Mutex<CellGuard>>,
51}
52
53impl<T> iCell<T> {
54    /// Creates a new iCell with the given value and freeze state.
55    ///
56    /// The new cell will be owned by the current thread. If `freeze` is `true`,
57    /// the cell will be initially locked and ownership cannot be transferred
58    /// until it is unfrozen.
59    ///
60    /// # Arguments
61    /// - `value`: The value to wrap in the cell
62    /// - `freeze`: Initial lock state (false = unlocked)
63    ///
64    /// # Returns
65    /// A new `iCell` owned by the current thread.
66    ///
67    /// # Examples
68    /// ```
69    /// use ibag::iCell;
70    /// let cell = iCell::new(42, false);
71    /// ```
72    pub fn new(value: T,freeze: bool) -> Self {
73        let guard = CellGuard {
74            freeze,
75            thread_id: thread::current().id(),
76        };
77
78        iCell {
79            value: ManuallyDrop::new(value),
80            guard: Arc::new(Mutex::new(guard)),
81        }
82    }
83
84    /// Attempts to take ownership of the cell from another thread.
85    ///
86    /// This method must be called from the thread that wants to take ownership.
87    /// If successful, the cell will be marked as owned by the current thread.
88    ///
89    /// # Returns
90    /// - `Ok(true)` if ownership was successfully transferred
91    /// - `Err(FailTakeOwnership)` if the cell is already frozen
92    ///
93    /// # Safety
94    /// The caller must ensure this is called from the new owning thread.
95    ///
96    /// # Examples
97    /// ```
98    /// use std::thread;
99    /// use ibag::iCell;
100    ///
101    /// let cell = iCell::new(42, false);
102    /// thread::spawn(move || {
103    ///     cell.take_ownership().unwrap();
104    ///     // Now this thread owns the cell
105    /// });
106    /// ```
107    pub fn take_ownership(&self) -> Result<bool, FailTakeOwnership>{
108        let mut guard = self.guard.lock().unwrap();
109        if guard.freeze {
110            return Err(FailTakeOwnership);
111        }
112        guard.freeze = true;
113        guard.thread_id = thread::current().id();
114        Ok(true)
115    }
116
117    /// Checks if the current thread is the valid owner of the cell.
118    ///
119    /// This is used internally to verify thread access permissions before
120    /// allowing operations on the contained value.
121    ///
122    /// # Returns
123    /// `true` if the current thread owns the cell, `false` otherwise.
124    ///
125    /// # Examples
126    /// ```
127    /// use ibag::iCell;
128    /// let cell = iCell::new(42, false);
129    /// assert!(cell.is_valid());
130    /// ```
131    pub fn is_valid(&self) -> bool {
132        let owner = self.guard.lock().unwrap().thread_id;
133        thread::current().id() == owner
134    }
135
136    #[inline(always)]
137    fn assert_thread(&self) {
138        if !self.is_valid() {
139            panic!("trying to access wrapped value in fragile container from incorrect thread.");
140        }
141    }
142
143    /// Consumes the iCell and returns the wrapped value.
144    ///
145    /// This is an unsafe operation that bypasses thread safety checks.
146    /// The caller must ensure this is called from the owning thread.
147    ///
148    /// # Safety
149    /// - Must be called from the owning thread (panics otherwise)
150    /// - The returned value is no longer protected by thread ownership rules
151    ///
152    /// # Examples
153    /// ```
154    /// use ibag::iCell;
155    /// let cell = iCell::new(42, false);
156    /// let value = unsafe { cell.into_inner() };
157    /// ```
158    pub fn into_inner(self) -> T {
159        self.assert_thread();
160        let mut this = ManuallyDrop::new(self);
161
162        unsafe { ManuallyDrop::take(&mut this.value) }
163    }
164
165    /// Attempts to consume the iCell and return the wrapped value.
166    ///
167    /// This is a safe alternative to `into_inner()` that returns the
168    /// original cell if called from the wrong thread.
169    ///
170    /// # Returns
171    /// - `Ok(T)` if called from the owning thread
172    /// - `Err(Self)` if called from a non-owning thread
173    ///
174    /// # Examples
175    /// ```
176    /// use ibag::iCell;
177    /// use std::thread;
178    ///
179    /// let cell = iCell::new(42, false);
180    /// let result = cell.try_into_inner();
181    /// assert!(result.is_ok());
182    ///
183    /// let cell = iCell::new(42, false);
184    /// thread::spawn(move || {
185    ///     let result = cell.try_into_inner();
186    ///     assert!(result.is_err());
187    /// });
188    /// ```
189    pub fn try_into_inner(self) -> Result<T, Self> {
190        if self.is_valid() {
191            Ok(self.into_inner())
192        } else {
193            Err(self)
194        }
195    }
196
197    /// Returns an immutable reference to the wrapped value
198    /// # Safety
199    /// - Must be called from the owning thread (panics otherwise)
200    fn get(&self) -> &T {
201        self.assert_thread();
202        &self.value
203    }
204
205    /// Returns a mutable reference to the wrapped value
206    /// # Safety
207    /// - Must be called from the owning thread (panics otherwise)
208    fn get_mut(&mut self) -> &mut T {
209        self.assert_thread();
210        &mut self.value
211    }
212
213    /// Attempts to get an immutable reference to the wrapped value
214    /// - Returns Ok(&T) if called from the owning thread
215    /// - Returns Err(InvalidThreadAccess) if called from a non-owning thread
216    /// Unlike get(), this is a safe operation that doesn't panic
217    pub fn try_get(&self) -> Result<&T, InvalidThreadAccess> {
218        if self.is_valid() {
219            Ok(&*self.value)
220        } else {
221            Err(InvalidThreadAccess)
222        }
223    }
224
225    /// Attempts to get a mutable reference to the wrapped value
226    /// - Returns Ok(&mut T) if called from the owning thread
227    /// - Returns Err(InvalidThreadAccess) if called from a non-owning thread
228    /// Unlike get_mut(), this is a safe operation that doesn't panic
229    pub fn try_get_mut(&mut self) -> Result<&mut T, InvalidThreadAccess> {
230        if self.is_valid() {
231            Ok(self.get_mut())
232        } else {
233            Err(InvalidThreadAccess)
234        }
235    }
236}
237
238impl<T> Drop for iCell<T> {
239    #[track_caller]
240    fn drop(&mut self) {
241        if mem::needs_drop::<T>() {
242            if self.is_valid() {
243                unsafe { ManuallyDrop::drop(&mut self.value) };
244            } else {
245                panic!("destructor of fragile object ran on wrong thread");
246            }
247        }
248    }
249}
250
251impl<T> From<T> for iCell<T> {
252    #[inline]
253    fn from(t: T) -> iCell<T> {
254        iCell::new(t, false)
255    }
256}
257
258impl<T: Clone> Clone for iCell<T> {
259    #[inline]
260    fn clone(&self) -> iCell<T> {
261        iCell::new(self.get().clone(), false)
262    }
263}
264
265impl<T: Default> Default for iCell<T> {
266    #[inline]
267    fn default() -> iCell<T> {
268        iCell::new(T::default(), false)
269    }
270}
271
272impl<T: PartialEq> PartialEq for iCell<T> {
273    #[inline]
274    fn eq(&self, other: &iCell<T>) -> bool {
275        *self.get() == *other.get()
276    }
277}
278
279impl<T: Eq> Eq for iCell<T> {}
280
281impl<T: PartialOrd> PartialOrd for iCell<T> {
282    #[inline]
283    fn partial_cmp(&self, other: &iCell<T>) -> Option<cmp::Ordering> {
284        self.get().partial_cmp(other.get())
285    }
286
287    #[inline]
288    fn lt(&self, other: &iCell<T>) -> bool {
289        *self.get() < *other.get()
290    }
291
292    #[inline]
293    fn le(&self, other: &iCell<T>) -> bool {
294        *self.get() <= *other.get()
295    }
296
297    #[inline]
298    fn gt(&self, other: &iCell<T>) -> bool {
299        *self.get() > *other.get()
300    }
301
302    #[inline]
303    fn ge(&self, other: &iCell<T>) -> bool {
304        *self.get() >= *other.get()
305    }
306}
307
308impl<T: Ord> Ord for iCell<T> {
309    #[inline]
310    fn cmp(&self, other: &iCell<T>) -> cmp::Ordering {
311        self.get().cmp(other.get())
312    }
313}
314
315impl<T: fmt::Display> fmt::Display for iCell<T> {
316    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
317        fmt::Display::fmt(self.get(), f)
318    }
319}
320
321impl<T: fmt::Debug> fmt::Debug for iCell<T> {
322    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
323        match self.try_get() {
324            Ok(value) => f.debug_struct("Fragile").field("value", value).finish(),
325            Err(..) => {
326                struct InvalidPlaceholder;
327                impl fmt::Debug for InvalidPlaceholder {
328                    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
329                        f.write_str("<invalid thread>")
330                    }
331                }
332
333                f.debug_struct("Fragile")
334                    .field("value", &InvalidPlaceholder)
335                    .finish()
336            }
337        }
338    }
339}
340
341// this type is sync because access can only ever happy from the same thread
342// that created it originally.  All other threads will be able to safely
343// call some basic operations on the reference and they will fail.
344unsafe impl<T> Sync for iCell<T> {}
345
346// The entire point of this type is to be Send
347#[allow(clippy::non_send_fields_in_send_ty)]
348unsafe impl<T> Send for iCell<T> {}
349
350#[test]
351fn test_basic() {
352    use std::thread;
353    let val = iCell::new(true, false);
354    assert_eq!(val.to_string(), "true");
355    assert_eq!(val.get(), &true);
356    assert!(val.try_get().is_ok());
357    thread::spawn(move || {
358        assert!(val.try_get().is_err());
359    })
360    .join()
361    .unwrap();
362}
363
364#[test]
365fn test_mut() {
366    let mut val = iCell::new(true, false);
367    *val.get_mut() = false;
368    assert_eq!(val.to_string(), "false");
369    assert_eq!(val.get(), &false);
370}
371
372#[test]
373#[should_panic]
374fn test_access_other_thread() {
375    use std::thread;
376    let val = iCell::new(true, false);
377    thread::spawn(move || {
378        val.get();
379    })
380    .join()
381    .unwrap();
382}
383
384#[test]
385fn test_noop_drop_elsewhere() {
386    use std::thread;
387    let val = iCell::new(true, false);
388    thread::spawn(move || {
389        // force the move
390        val.try_get().ok();
391    })
392    .join()
393    .unwrap();
394}
395
396#[test]
397fn test_panic_on_drop_elsewhere() {
398    use std::sync::atomic::{AtomicBool, Ordering};
399    use std::sync::Arc;
400    use std::thread;
401    let was_called = Arc::new(AtomicBool::new(false));
402    struct X(Arc<AtomicBool>);
403    impl Drop for X {
404        fn drop(&mut self) {
405            self.0.store(true, Ordering::SeqCst);
406        }
407    }
408    let val = iCell::new(X(was_called.clone()), false);
409    assert!(thread::spawn(move || {
410        val.try_get().ok();
411    })
412    .join()
413    .is_err());
414    assert!(!was_called.load(Ordering::SeqCst));
415}
416
417#[test]
418fn test_rc_sending() {
419    use std::rc::Rc;
420    use std::sync::mpsc::channel;
421    use std::thread;
422
423    let val = iCell::new(Rc::new(true), false);
424    let (tx, rx) = channel();
425
426    let thread = thread::spawn(move || {
427        assert!(val.try_get().is_err());
428        let here = val;
429        tx.send(here).unwrap();
430    });
431
432    let rv = rx.recv().unwrap();
433    assert!(**rv.get());
434
435    thread.join().unwrap();
436}
437
438#[test]
439fn test_rc_sending_take_ownership() {
440    use std::rc::Rc;
441    use std::sync::mpsc::channel;
442    use std::thread;
443
444    let val = iCell::new(Rc::new(true), false);
445    let (tx, rx) = channel();
446
447    let sender = thread::spawn(move || {
448        assert!(val.try_get().is_err());
449        let here = val;
450        tx.send(here).unwrap();
451    });
452
453    let recv = thread::spawn(move || {
454        let rv = rx.recv().unwrap();
455        let r = rv.take_ownership();
456        match r {
457            Ok(_) => {},
458            Err(_) => panic!("failed to take ownership"),
459        }
460        assert!(**rv.get());
461    });
462
463    recv.join().unwrap();
464    sender.join().unwrap();
465}
466
467#[test]
468fn test_use_after_free() {
469    use std::rc::Rc;
470    use std::sync::mpsc::channel;
471    use std::thread;
472
473    let val = iCell::new(Rc::new(true), false);
474    let (tx, rx) = channel();
475
476    let sender = thread::spawn(move || {
477        assert!(val.try_get().is_err());
478        let here = val;
479        tx.send(here).unwrap();
480    });
481
482    sender.join().unwrap();
483
484    let recv = thread::spawn(move || {
485        let rv = rx.recv().unwrap();
486        let r = rv.take_ownership();
487        match r {
488            Ok(_) => {},
489            Err(_) => panic!("failed to take ownership"),
490        }
491        assert!(**rv.get());
492    });
493
494    recv.join().unwrap();
495}