Skip to main content

fragile/
sticky.rs

1use std::cmp;
2use std::fmt;
3use std::marker::{PhantomData, PhantomPinned};
4use std::mem;
5
6use crate::errors::InvalidThreadAccess;
7use crate::registry;
8use crate::thread_id::{self, ThreadId};
9
10/// A [`Sticky<T>`] keeps a value T stored in a thread.
11///
12/// This type works similar in nature to [`Fragile`](crate::Fragile) and exposes a
13/// similar interface.  The difference is that whereas [`Fragile`](crate::Fragile) has
14/// its destructor called in the thread where the value was sent, a
15/// [`Sticky`] that is moved to another thread will have the internal
16/// destructor called when the originating thread tears down.
17///
18/// Because [`Sticky`] allows values to be kept alive for longer than the
19/// [`Sticky`] itself, it requires all its contents to be `'static` for
20/// soundness. Access to the value is limited to scoped callbacks so references
21/// cannot outlive the callback or the originating thread.
22///
23/// As this uses TLS internally the general rules about the platform limitations
24/// of destructors for TLS apply.
25///
26/// A `Sticky<T>` is [`Unpin`] only if `T` is `Unpin`. This ensures that once a
27/// wrapped value has been pinned through the `Future` or `Stream`
28/// implementation, it cannot later be moved out of the wrapper.
29pub struct Sticky<T: 'static> {
30    item_id: registry::ItemId,
31    thread_id: ThreadId,
32    _marker: PhantomData<*mut T>,
33    _pin: PhantomPinned,
34}
35
36impl<T: Unpin> Unpin for Sticky<T> {}
37
38impl<T> Drop for Sticky<T> {
39    #[track_caller]
40    fn drop(&mut self) {
41        // The value and its allocation can only be released on the originating
42        // thread while its registry is alive. Otherwise the registry retains
43        // the entry until the thread dies, when it releases it itself.
44        if self.is_valid() {
45            // SAFETY: This is the originating thread and the wrapper is being
46            // consumed, so no safe references to the entry can remain.
47            unsafe { self.unsafe_drop_value() };
48        }
49    }
50}
51
52impl<T> Sticky<T> {
53    /// Creates a new [`Sticky`] wrapping a `value`.
54    ///
55    /// The value that is moved into the [`Sticky`] can be non `Send` and
56    /// will be anchored to the thread that created the object.  If the
57    /// sticky wrapper type ends up being send from thread to thread
58    /// only the original thread can interact with the value.
59    pub fn new(value: T) -> Self {
60        let entry = registry::Entry {
61            ptr: Box::into_raw(Box::new(value)).cast(),
62            drop: |ptr| {
63                let ptr = ptr.cast::<T>();
64                // SAFETY: This callback will only be called once, with the
65                // above pointer.
66                drop(unsafe { Box::from_raw(ptr) });
67            },
68        };
69
70        let thread_id = thread_id::current();
71        let item_id = registry::insert(entry);
72
73        Sticky {
74            item_id,
75            thread_id,
76            _marker: PhantomData,
77            _pin: PhantomPinned,
78        }
79    }
80
81    #[inline(always)]
82    #[track_caller]
83    fn value_ptr(&self) -> *mut T {
84        self.assert_thread();
85        match registry::try_get(self.item_id) {
86            Some(ptr) => ptr.cast::<T>(),
87            None => registry_destroyed(),
88        }
89    }
90
91    #[inline(always)]
92    fn try_value_ptr(&self) -> Result<*mut T, InvalidThreadAccess> {
93        if !self.is_on_thread() {
94            return Err(InvalidThreadAccess);
95        }
96        match registry::try_get(self.item_id) {
97            Some(ptr) => Ok(ptr.cast::<T>()),
98            None => Err(InvalidThreadAccess),
99        }
100    }
101
102    #[inline(always)]
103    fn is_on_thread(&self) -> bool {
104        thread_id::current() == self.thread_id
105    }
106
107    /// Returns `true` if the access is valid.
108    ///
109    /// This will be `false` if the value was sent to another thread.  It is
110    /// also `false` once its originating registry starts being destroyed,
111    /// even on platforms that subsequently reinitialize thread local storage.
112    #[inline(always)]
113    pub fn is_valid(&self) -> bool {
114        self.try_value_ptr().is_ok()
115    }
116
117    #[inline(always)]
118    #[track_caller]
119    fn assert_thread(&self) {
120        if !self.is_on_thread() {
121            panic!("trying to access wrapped value in sticky container from incorrect thread.");
122        }
123    }
124
125    /// Consumes the `Sticky`, returning the wrapped value.
126    ///
127    /// # Panics
128    ///
129    /// Panics if called from a different thread than the one where the
130    /// original value was created, or if that thread is already destroying
131    /// its thread local storage.
132    #[track_caller]
133    pub fn into_inner(mut self) -> T {
134        self.assert_thread();
135        // SAFETY: Ownership and the successful thread check ensure there are
136        // no outstanding safe references when the registry entry is removed.
137        unsafe {
138            let rv = self.unsafe_take_value();
139            mem::forget(self);
140            rv
141        }
142    }
143
144    unsafe fn unsafe_drop_value(&mut self) {
145        // The registry is unavailable while its TLS destructor is running. In
146        // that case it still owns the entry and will drop the value itself.
147        if let Some(entry) = registry::try_remove(self.item_id) {
148            // SAFETY: The callback is paired with this entry's pointer and the
149            // removal ensures it can only be called once.
150            unsafe { (entry.drop)(entry.ptr) };
151        }
152    }
153
154    #[track_caller]
155    unsafe fn unsafe_take_value(&mut self) -> T {
156        let ptr = match registry::try_remove(self.item_id) {
157            Some(entry) => entry.ptr.cast::<T>(),
158            None => registry_destroyed(),
159        };
160        // SAFETY: This is the pointer created for `T` in `new`, and removing
161        // the entry transfers its allocation to this call exactly once.
162        unsafe { *Box::from_raw(ptr) }
163    }
164
165    /// Consumes the `Sticky`, returning the wrapped value if successful.
166    ///
167    /// The wrapped value is returned if this is called from the same thread
168    /// as the one where the original value was created, otherwise the
169    /// `Sticky` is returned as `Err(self)`.
170    pub fn try_into_inner(self) -> Result<T, Self> {
171        if self.is_valid() {
172            Ok(self.into_inner())
173        } else {
174            Err(self)
175        }
176    }
177
178    /// Invokes a callback with a shared reference to the wrapped value.
179    ///
180    /// The callback may return owned data, but it cannot return a reference
181    /// derived from the wrapped value. This keeps every borrow within the
182    /// lifetime of the originating thread.
183    ///
184    /// ```
185    /// use fragile::Sticky;
186    ///
187    /// let value = Sticky::new(String::from("hello"));
188    /// let length = value.with(String::len);
189    /// assert_eq!(length, 5);
190    /// ```
191    ///
192    /// A future borrowing the value cannot escape the callback. Clone or
193    /// otherwise create owned data before returning a future:
194    ///
195    /// ```
196    /// use fragile::Sticky;
197    ///
198    /// let value = Sticky::new(String::from("hello"));
199    /// let future = value.with(|value| {
200    ///     let value = value.clone();
201    ///     async move {
202    ///         std::future::ready(()).await;
203    ///         assert_eq!(value, "hello");
204    ///     }
205    /// });
206    /// ```
207    ///
208    /// # Panics
209    ///
210    /// Panics if the calling thread is not the one that wrapped the value, or
211    /// if that thread is already destroying its thread local storage.
212    /// For a non-panicking variant, use [`try_with`](Self::try_with).
213    #[track_caller]
214    pub fn with<R, F>(&self, f: F) -> R
215    where
216        F: for<'a> FnOnce(&'a T) -> R,
217    {
218        // SAFETY: The registry entry owns a live `T`. The higher-ranked
219        // callback bound prevents this reference from escaping the call.
220        f(unsafe { &*self.value_ptr() })
221    }
222
223    /// Invokes a callback with an exclusive reference to the wrapped value.
224    ///
225    /// The callback may return owned data, but it cannot return a reference
226    /// derived from the wrapped value.
227    ///
228    /// ```
229    /// use fragile::Sticky;
230    ///
231    /// let mut value = Sticky::new(String::from("hello"));
232    /// value.with_mut(|value| value.push_str(" world"));
233    /// assert_eq!(value.with(Clone::clone), "hello world");
234    /// ```
235    ///
236    /// # Panics
237    ///
238    /// Panics if the calling thread is not the one that wrapped the value, or
239    /// if that thread is already destroying its thread local storage.
240    /// For a non-panicking variant, use [`try_with_mut`](Self::try_with_mut).
241    #[track_caller]
242    pub fn with_mut<R, F>(&mut self, f: F) -> R
243    where
244        F: for<'a> FnOnce(&'a mut T) -> R,
245    {
246        // SAFETY: The registry entry owns a live `T`, `self` is borrowed
247        // exclusively, and the callback bound prevents the reference escaping.
248        f(unsafe { &mut *self.value_ptr() })
249    }
250
251    /// Invokes a callback with a shared reference to the wrapped value.
252    ///
253    /// As with [`with`](Self::with), the callback cannot return a reference
254    /// derived from the wrapped value.
255    ///
256    /// Returns [`InvalidThreadAccess`] without invoking the callback if
257    /// [`is_valid`](Self::is_valid) is `false`, that is when called from a
258    /// thread other than the one that wrapped the value or while that thread
259    /// is destroying its thread local storage.
260    pub fn try_with<R, F>(&self, f: F) -> Result<R, InvalidThreadAccess>
261    where
262        F: for<'a> FnOnce(&'a T) -> R,
263    {
264        let ptr = self.try_value_ptr()?;
265        // SAFETY: See `with`.
266        Ok(f(unsafe { &*ptr }))
267    }
268
269    /// Invokes a callback with an exclusive reference to the wrapped value.
270    ///
271    /// As with [`with_mut`](Self::with_mut), the callback cannot return a
272    /// reference derived from the wrapped value.
273    ///
274    /// Returns [`InvalidThreadAccess`] without invoking the callback if
275    /// [`is_valid`](Self::is_valid) is `false`, that is when called from a
276    /// thread other than the one that wrapped the value or while that thread
277    /// is destroying its thread local storage.
278    pub fn try_with_mut<R, F>(&mut self, f: F) -> Result<R, InvalidThreadAccess>
279    where
280        F: for<'a> FnOnce(&'a mut T) -> R,
281    {
282        let ptr = self.try_value_ptr()?;
283        // SAFETY: See `with_mut`.
284        Ok(f(unsafe { &mut *ptr }))
285    }
286}
287
288#[cold]
289#[track_caller]
290fn registry_destroyed() -> ! {
291    panic!("trying to access wrapped value in sticky container while its thread's local storage is being destroyed.");
292}
293
294impl<T> From<T> for Sticky<T> {
295    #[inline]
296    fn from(t: T) -> Sticky<T> {
297        Sticky::new(t)
298    }
299}
300
301impl<T: Clone> Clone for Sticky<T> {
302    #[inline]
303    #[track_caller]
304    fn clone(&self) -> Sticky<T> {
305        Sticky::new(self.with(Clone::clone))
306    }
307}
308
309impl<T: Default> Default for Sticky<T> {
310    #[inline]
311    fn default() -> Sticky<T> {
312        Sticky::new(T::default())
313    }
314}
315
316impl<T: PartialEq> PartialEq for Sticky<T> {
317    #[inline]
318    #[track_caller]
319    fn eq(&self, other: &Sticky<T>) -> bool {
320        self.with(|this| other.with(|other| this == other))
321    }
322}
323
324impl<T: Eq> Eq for Sticky<T> {}
325
326impl<T: PartialOrd> PartialOrd for Sticky<T> {
327    #[inline]
328    #[track_caller]
329    fn partial_cmp(&self, other: &Sticky<T>) -> Option<cmp::Ordering> {
330        self.with(|this| other.with(|other| this.partial_cmp(other)))
331    }
332
333    #[inline]
334    #[track_caller]
335    fn lt(&self, other: &Sticky<T>) -> bool {
336        self.with(|this| other.with(|other| this < other))
337    }
338
339    #[inline]
340    #[track_caller]
341    fn le(&self, other: &Sticky<T>) -> bool {
342        self.with(|this| other.with(|other| this <= other))
343    }
344
345    #[inline]
346    #[track_caller]
347    fn gt(&self, other: &Sticky<T>) -> bool {
348        self.with(|this| other.with(|other| this > other))
349    }
350
351    #[inline]
352    #[track_caller]
353    fn ge(&self, other: &Sticky<T>) -> bool {
354        self.with(|this| other.with(|other| this >= other))
355    }
356}
357
358impl<T: Ord> Ord for Sticky<T> {
359    #[inline]
360    #[track_caller]
361    fn cmp(&self, other: &Sticky<T>) -> cmp::Ordering {
362        self.with(|this| other.with(|other| this.cmp(other)))
363    }
364}
365
366impl<T: fmt::Display> fmt::Display for Sticky<T> {
367    #[track_caller]
368    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
369        self.with(|value| fmt::Display::fmt(value, f))
370    }
371}
372
373impl<T: fmt::Debug> fmt::Debug for Sticky<T> {
374    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
375        if self.is_valid() {
376            self.with(|value| f.debug_struct("Sticky").field("value", value).finish())
377        } else {
378            struct InvalidPlaceholder;
379            impl fmt::Debug for InvalidPlaceholder {
380                fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
381                    f.write_str("<invalid thread>")
382                }
383            }
384
385            f.debug_struct("Sticky")
386                .field("value", &InvalidPlaceholder)
387                .finish()
388        }
389    }
390}
391
392// SAFETY: The inner value remains in its originating thread's registry and can
393// only be accessed there. Other threads inspect only the stored thread ID.
394unsafe impl<T> Sync for Sticky<T> {}
395
396// SAFETY: Moving the wrapper moves only registry metadata. The inner value is
397// accessed and destroyed exclusively on its originating thread.
398unsafe impl<T> Send for Sticky<T> {}
399
400#[test]
401fn test_basic() {
402    use std::thread;
403    let val = Sticky::new(true);
404    assert_eq!(val.to_string(), "true");
405    assert!(val.with(|value| *value));
406    assert!(val.try_with(|value| *value).unwrap());
407    let external = String::from("external");
408    assert_eq!(val.with(|_| external.as_str()), "external");
409    thread::spawn(move || {
410        assert!(val
411            .try_with(|_| panic!("callback ran on the wrong thread"))
412            .is_err());
413    })
414    .join()
415    .unwrap();
416}
417
418#[test]
419fn test_mut() {
420    let mut val = Sticky::new(true);
421    val.with_mut(|value| *value = false);
422    assert_eq!(val.to_string(), "false");
423    assert!(!val.with(|value| *value));
424    assert!(val
425        .try_with_mut(|value| {
426            *value = true;
427            *value
428        })
429        .unwrap());
430}
431
432#[test]
433#[should_panic]
434fn test_access_other_thread() {
435    use std::thread;
436    let mut val = Sticky::new(true);
437    thread::spawn(move || {
438        assert!(val
439            .try_with_mut(|_| panic!("mutable callback ran on the wrong thread"))
440            .is_err());
441        val.with(|_| ());
442    })
443    .join()
444    .unwrap();
445}
446
447#[test]
448fn test_drop_same_thread() {
449    use std::sync::atomic::{AtomicBool, Ordering};
450    use std::sync::Arc;
451    let was_called = Arc::new(AtomicBool::new(false));
452    struct X(Arc<AtomicBool>);
453    impl Drop for X {
454        fn drop(&mut self) {
455            self.0.store(true, Ordering::SeqCst);
456        }
457    }
458    let val = Sticky::new(X(was_called.clone()));
459    mem::drop(val);
460    assert!(was_called.load(Ordering::SeqCst));
461}
462
463#[test]
464fn test_noop_drop_elsewhere() {
465    use std::sync::atomic::{AtomicBool, Ordering};
466    use std::sync::Arc;
467    use std::thread;
468
469    let was_called = Arc::new(AtomicBool::new(false));
470
471    {
472        let was_called = was_called.clone();
473        thread::spawn(move || {
474            struct X(Arc<AtomicBool>);
475            impl Drop for X {
476                fn drop(&mut self) {
477                    self.0.store(true, Ordering::SeqCst);
478                }
479            }
480
481            let val = Sticky::new(X(was_called.clone()));
482            val.with(|_| ());
483            assert!(thread::spawn(move || {
484                // moves it here but do not deallocate
485                val.try_with(|_| ()).ok();
486            })
487            .join()
488            .is_ok());
489
490            assert!(!was_called.load(Ordering::SeqCst));
491        })
492        .join()
493        .unwrap();
494    }
495
496    assert!(was_called.load(Ordering::SeqCst));
497}
498
499#[test]
500fn test_rc_sending() {
501    use std::rc::Rc;
502    use std::thread;
503    let val = Sticky::new(Rc::new(true));
504    thread::spawn(move || {
505        assert!(val
506            .try_with(|_| panic!("callback ran on the wrong thread"))
507            .is_err());
508    })
509    .join()
510    .unwrap();
511}
512
513#[test]
514fn test_two_stickies() {
515    struct Wat;
516
517    impl Drop for Wat {
518        fn drop(&mut self) {
519            // do nothing
520        }
521    }
522
523    let s1 = Sticky::new(Wat);
524    let s2 = Sticky::new(Wat);
525
526    // make sure all is well
527
528    drop(s1);
529    drop(s2);
530}
531
532#[test]
533fn test_registry_mutation_during_callback() {
534    let first = Sticky::new(String::from("first"));
535    let second = Sticky::new(String::from("second"));
536
537    first.with(|first| {
538        drop(second);
539        let third = Sticky::new(String::from("third"));
540        assert_eq!(third.with(|value| value.clone()), "third");
541        assert_eq!(first, "first");
542    });
543}
544
545#[test]
546fn test_thread_spawn() {
547    use crate::Sticky;
548    use std::{mem::ManuallyDrop, thread};
549
550    let dummy_sticky = thread::spawn(|| Sticky::new(())).join().unwrap();
551    let sticky_string = ManuallyDrop::new(Sticky::new(String::from("Hello World")));
552
553    sticky_string.with(|hello| {
554        assert_eq!(hello, "Hello World");
555        drop(dummy_sticky);
556        assert_eq!(hello, "Hello World");
557    });
558}
559
560#[test]
561fn test_no_drop_glue_released_eagerly() {
562    assert!(!mem::needs_drop::<u32>());
563    let before = registry::len();
564    for value in 0..64u32 {
565        let sticky = Sticky::new(value);
566        assert_eq!(registry::len(), before + 1);
567        assert_eq!(sticky.with(|value| *value), value);
568    }
569    assert_eq!(registry::len(), before);
570
571    let sticky = Sticky::new(1u32);
572    assert_eq!(sticky.into_inner(), 1);
573    assert_eq!(registry::len(), before);
574}
575
576#[test]
577fn test_drop_in_tls_destructor() {
578    use std::cell::RefCell;
579    use std::sync::atomic::{AtomicUsize, Ordering};
580    use std::sync::Arc;
581    use std::thread;
582
583    struct X(Arc<AtomicUsize>);
584
585    impl Drop for X {
586        fn drop(&mut self) {
587            self.0.fetch_add(1, Ordering::SeqCst);
588        }
589    }
590
591    thread_local!(static SLOT: RefCell<Option<Sticky<X>>> = RefCell::new(None));
592
593    let drop_count = Arc::new(AtomicUsize::new(0));
594    let thread_drop_count = drop_count.clone();
595
596    // Dropping the wrapper from a thread local destructor must neither panic
597    // nor abort, regardless of the order in which destructors run.
598    thread::spawn(move || {
599        SLOT.with(|slot| *slot.borrow_mut() = Some(Sticky::new(X(thread_drop_count))));
600    })
601    .join()
602    .unwrap();
603
604    assert_eq!(drop_count.load(Ordering::SeqCst), 1);
605}
606
607#[test]
608fn test_access_during_registry_teardown() {
609    use std::sync::atomic::{AtomicUsize, Ordering};
610    use std::sync::Arc;
611    use std::thread;
612
613    struct Outcome {
614        errors: AtomicUsize,
615        dropped: AtomicUsize,
616    }
617
618    struct AccessesSibling {
619        sibling: Sticky<String>,
620        outcome: Arc<Outcome>,
621    }
622
623    impl Drop for AccessesSibling {
624        fn drop(&mut self) {
625            // This runs from the registry destructor during thread shutdown.
626            // Non-panicking accessors must report an error instead of
627            // panicking, which would abort the process.
628            if !self.sibling.is_valid() {
629                self.outcome.errors.fetch_add(1, Ordering::SeqCst);
630            }
631            if self.sibling.try_with(|value| value.len()).is_err() {
632                self.outcome.errors.fetch_add(1, Ordering::SeqCst);
633            }
634            if self.sibling.try_with_mut(|value| value.len()).is_err() {
635                self.outcome.errors.fetch_add(1, Ordering::SeqCst);
636            }
637            if format!("{:?}", self.sibling).contains("<invalid thread>") {
638                self.outcome.errors.fetch_add(1, Ordering::SeqCst);
639            }
640            self.outcome.dropped.fetch_add(1, Ordering::SeqCst);
641        }
642    }
643
644    let outcome = Arc::new(Outcome {
645        errors: AtomicUsize::new(0),
646        dropped: AtomicUsize::new(0),
647    });
648    let thread_outcome = outcome.clone();
649
650    thread::spawn(move || {
651        let value = Sticky::new(AccessesSibling {
652            sibling: Sticky::new(String::from("sibling")),
653            outcome: thread_outcome,
654        });
655        // Move the wrapper away so the value is only released by the
656        // registry when the originating thread shuts down.
657        thread::spawn(move || drop(value)).join().unwrap();
658    })
659    .join()
660    .unwrap();
661
662    assert_eq!(outcome.errors.load(Ordering::SeqCst), 4);
663    assert_eq!(outcome.dropped.load(Ordering::SeqCst), 1);
664}
665
666#[test]
667fn test_nested_sticky_drop_at_thread_exit() {
668    use std::sync::atomic::{AtomicUsize, Ordering};
669    use std::sync::Arc;
670    use std::thread;
671
672    let drop_count = Arc::new(AtomicUsize::new(0));
673    let thread_drop_count = drop_count.clone();
674
675    thread::spawn(move || {
676        struct X(Arc<AtomicUsize>);
677
678        impl Drop for X {
679            fn drop(&mut self) {
680                self.0.fetch_add(1, Ordering::SeqCst);
681            }
682        }
683
684        let outer = Sticky::new(Sticky::new(X(thread_drop_count)));
685        thread::spawn(move || drop(outer)).join().unwrap();
686    })
687    .join()
688    .unwrap();
689
690    assert_eq!(drop_count.load(Ordering::SeqCst), 1);
691}