Skip to main content

fragile/
fragile.rs

1use std::cmp;
2use std::fmt;
3use std::mem;
4use std::mem::ManuallyDrop;
5#[cfg(feature = "future")]
6use std::pin::Pin;
7
8use crate::errors::InvalidThreadAccess;
9use crate::thread_id::{self, ThreadId};
10
11/// The value starts inline and is moved to stable storage before the first
12/// pinned projection.
13enum FragileValue<T> {
14    Inline(T),
15    #[cfg(feature = "future")]
16    Pinned(Pin<Box<T>>),
17    #[cfg(feature = "future")]
18    Taken,
19}
20
21/// A [`Fragile<T>`] wraps a non sendable `T` to be safely send to other threads.
22///
23/// Once the value has been wrapped it can be sent to other threads but access
24/// to the value on those threads will fail.
25///
26/// If the value needs destruction and the fragile wrapper is on another thread
27/// the destructor will panic.  Alternatively you can use
28/// [`Sticky`](crate::Sticky), which does not panic and instead retains the
29/// value until the originating thread exits.
30///
31/// Polling a `Fragile` as a `Future` or `Stream` moves its value to stable heap
32/// storage before the first poll. If a polled `Fragile` with drop glue is dropped
33/// on another thread, that storage is leaked to preserve the value's pinning
34/// invariant. A `Fragile<T>` is `Unpin` only if `T` is `Unpin`.
35pub struct Fragile<T> {
36    // ManuallyDrop is necessary because we need to move out of here without running the
37    // Drop code in functions like `into_inner`, and to leak pinned storage when dropped
38    // on the wrong thread.
39    value: ManuallyDrop<FragileValue<T>>,
40    // Thread IDs are unique for the duration of the process and stay readable
41    // during thread local storage teardown.
42    thread_id: ThreadId,
43}
44
45impl<T> Fragile<T> {
46    /// Creates a new [`Fragile`] wrapping a `value`.
47    ///
48    /// The value that is moved into the [`Fragile`] can be non `Send` and
49    /// will be anchored to the thread that created the object.  If the
50    /// fragile wrapper type ends up being send from thread to thread
51    /// only the original thread can interact with the value.
52    pub fn new(value: T) -> Self {
53        Fragile {
54            value: ManuallyDrop::new(FragileValue::Inline(value)),
55            thread_id: thread_id::current(),
56        }
57    }
58
59    /// Returns `true` if the access is valid.
60    ///
61    /// This will be `false` if the value was sent to another thread.
62    pub fn is_valid(&self) -> bool {
63        thread_id::current() == self.thread_id
64    }
65
66    #[inline(always)]
67    #[track_caller]
68    fn assert_thread(&self) {
69        if !self.is_valid() {
70            panic!("trying to access wrapped value in fragile container from incorrect thread.");
71        }
72    }
73
74    fn value(&self) -> &T {
75        match &*self.value {
76            FragileValue::Inline(value) => value,
77            #[cfg(feature = "future")]
78            FragileValue::Pinned(value) => value.as_ref().get_ref(),
79            #[cfg(feature = "future")]
80            FragileValue::Taken => unreachable!("fragile value is missing"),
81        }
82    }
83
84    fn value_mut(&mut self) -> &mut T {
85        match &mut *self.value {
86            FragileValue::Inline(value) => value,
87            // SAFETY: Safe code cannot obtain `&mut Fragile<T>` after the
88            // wrapper has been pinned unless `T` is `Unpin`.
89            #[cfg(feature = "future")]
90            FragileValue::Pinned(value) => unsafe { value.as_mut().get_unchecked_mut() },
91            #[cfg(feature = "future")]
92            FragileValue::Taken => unreachable!("fragile value is missing"),
93        }
94    }
95
96    #[cfg(feature = "future")]
97    #[track_caller]
98    pub(crate) fn get_pin_mut(self: Pin<&mut Self>) -> Pin<&mut T> {
99        // SAFETY: We do not move the wrapper. The inline value has never been
100        // exposed through a pinned projection and may be moved to stable
101        // storage; an already-pinned value is never moved.
102        let this = unsafe { self.get_unchecked_mut() };
103        this.assert_thread();
104
105        if matches!(&*this.value, FragileValue::Inline(_)) {
106            // Move the value to stable storage before projecting it as pinned.
107            let value = match mem::replace(&mut *this.value, FragileValue::Taken) {
108                FragileValue::Inline(value) => value,
109                _ => unreachable!(),
110            };
111            *this.value = FragileValue::Pinned(Box::pin(value));
112        }
113
114        match &mut *this.value {
115            FragileValue::Pinned(value) => value.as_mut(),
116            _ => unreachable!(),
117        }
118    }
119
120    /// Consumes the `Fragile`, returning the wrapped value.
121    ///
122    /// # Panics
123    ///
124    /// Panics if called from a different thread than the one where the
125    /// original value was created.
126    #[track_caller]
127    pub fn into_inner(self) -> T {
128        self.assert_thread();
129
130        let mut this = ManuallyDrop::new(self);
131
132        // SAFETY: `this` is not accessed beyond this point, and because it's in a ManuallyDrop its
133        // destructor is not run.
134        match unsafe { ManuallyDrop::take(&mut this.value) } {
135            FragileValue::Inline(value) => value,
136            #[cfg(feature = "future")]
137            FragileValue::Pinned(value) => {
138                // SAFETY: Safe code can only regain ownership of a previously
139                // pinned wrapper if `T` is `Unpin`.
140                *unsafe { Pin::into_inner_unchecked(value) }
141            }
142            #[cfg(feature = "future")]
143            FragileValue::Taken => unreachable!("fragile value is missing"),
144        }
145    }
146
147    /// Consumes the `Fragile`, returning the wrapped value if successful.
148    ///
149    /// The wrapped value is returned if this is called from the same thread
150    /// as the one where the original value was created, otherwise the
151    /// [`Fragile`] is returned as `Err(self)`.
152    pub fn try_into_inner(self) -> Result<T, Self> {
153        if self.is_valid() {
154            Ok(self.into_inner())
155        } else {
156            Err(self)
157        }
158    }
159
160    /// Immutably borrows the wrapped value.
161    ///
162    /// # Panics
163    ///
164    /// Panics if the calling thread is not the one that wrapped the value.
165    /// For a non-panicking variant, use [`try_get`](Self::try_get).
166    #[track_caller]
167    pub fn get(&self) -> &T {
168        self.assert_thread();
169        self.value()
170    }
171
172    /// Mutably borrows the wrapped value.
173    ///
174    /// # Panics
175    ///
176    /// Panics if the calling thread is not the one that wrapped the value.
177    /// For a non-panicking variant, use [`try_get_mut`](Self::try_get_mut).
178    #[track_caller]
179    pub fn get_mut(&mut self) -> &mut T {
180        self.assert_thread();
181        self.value_mut()
182    }
183
184    /// Tries to immutably borrow the wrapped value.
185    ///
186    /// Returns `None` if the calling thread is not the one that wrapped the value.
187    pub fn try_get(&self) -> Result<&T, InvalidThreadAccess> {
188        if self.is_valid() {
189            Ok(self.value())
190        } else {
191            Err(InvalidThreadAccess)
192        }
193    }
194
195    /// Tries to mutably borrow the wrapped value.
196    ///
197    /// Returns `None` if the calling thread is not the one that wrapped the value.
198    pub fn try_get_mut(&mut self) -> Result<&mut T, InvalidThreadAccess> {
199        if self.is_valid() {
200            Ok(self.value_mut())
201        } else {
202            Err(InvalidThreadAccess)
203        }
204    }
205}
206
207impl<T> Drop for Fragile<T> {
208    #[track_caller]
209    fn drop(&mut self) {
210        // Values without drop glue can be released on any thread, so the
211        // thread check is skipped for them.
212        if !mem::needs_drop::<T>() || self.is_valid() {
213            // SAFETY: `ManuallyDrop::drop` cannot be called after this point.
214            unsafe { ManuallyDrop::drop(&mut self.value) };
215        } else {
216            // If the value was pinned, leaving the `Pin<Box<T>>` in
217            // `ManuallyDrop` also keeps its storage alive. This is necessary
218            // because the destructor cannot run on this thread.
219            panic!("destructor of fragile object ran on wrong thread");
220        }
221    }
222}
223
224impl<T> From<T> for Fragile<T> {
225    #[inline]
226    fn from(t: T) -> Fragile<T> {
227        Fragile::new(t)
228    }
229}
230
231impl<T: Clone> Clone for Fragile<T> {
232    #[inline]
233    #[track_caller]
234    fn clone(&self) -> Fragile<T> {
235        Fragile::new(self.get().clone())
236    }
237}
238
239impl<T: Default> Default for Fragile<T> {
240    #[inline]
241    fn default() -> Fragile<T> {
242        Fragile::new(T::default())
243    }
244}
245
246impl<T: PartialEq> PartialEq for Fragile<T> {
247    #[inline]
248    #[track_caller]
249    fn eq(&self, other: &Fragile<T>) -> bool {
250        *self.get() == *other.get()
251    }
252}
253
254impl<T: Eq> Eq for Fragile<T> {}
255
256impl<T: PartialOrd> PartialOrd for Fragile<T> {
257    #[inline]
258    #[track_caller]
259    fn partial_cmp(&self, other: &Fragile<T>) -> Option<cmp::Ordering> {
260        self.get().partial_cmp(other.get())
261    }
262
263    #[inline]
264    #[track_caller]
265    fn lt(&self, other: &Fragile<T>) -> bool {
266        *self.get() < *other.get()
267    }
268
269    #[inline]
270    #[track_caller]
271    fn le(&self, other: &Fragile<T>) -> bool {
272        *self.get() <= *other.get()
273    }
274
275    #[inline]
276    #[track_caller]
277    fn gt(&self, other: &Fragile<T>) -> bool {
278        *self.get() > *other.get()
279    }
280
281    #[inline]
282    #[track_caller]
283    fn ge(&self, other: &Fragile<T>) -> bool {
284        *self.get() >= *other.get()
285    }
286}
287
288impl<T: Ord> Ord for Fragile<T> {
289    #[inline]
290    #[track_caller]
291    fn cmp(&self, other: &Fragile<T>) -> cmp::Ordering {
292        self.get().cmp(other.get())
293    }
294}
295
296impl<T: fmt::Display> fmt::Display for Fragile<T> {
297    #[track_caller]
298    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
299        fmt::Display::fmt(self.get(), f)
300    }
301}
302
303impl<T: fmt::Debug> fmt::Debug for Fragile<T> {
304    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
305        match self.try_get() {
306            Ok(value) => f.debug_struct("Fragile").field("value", value).finish(),
307            Err(..) => {
308                struct InvalidPlaceholder;
309                impl fmt::Debug for InvalidPlaceholder {
310                    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
311                        f.write_str("<invalid thread>")
312                    }
313                }
314
315                f.debug_struct("Fragile")
316                    .field("value", &InvalidPlaceholder)
317                    .finish()
318            }
319        }
320    }
321}
322
323// SAFETY: The inner value can only be accessed on its originating thread.
324// Shared operations on every other thread inspect only the thread ID and fail.
325unsafe impl<T> Sync for Fragile<T> {}
326
327// SAFETY: Moving the wrapper never accesses the inner value. Wrong-thread
328// destruction does not run `T`'s destructor and preserves pinned storage.
329#[allow(clippy::non_send_fields_in_send_ty)]
330unsafe impl<T> Send for Fragile<T> {}
331
332#[test]
333fn test_basic() {
334    use std::thread;
335    let val = Fragile::new(true);
336    assert_eq!(val.to_string(), "true");
337    assert_eq!(val.get(), &true);
338    assert!(val.try_get().is_ok());
339    thread::spawn(move || {
340        assert!(val.try_get().is_err());
341    })
342    .join()
343    .unwrap();
344}
345
346#[test]
347fn test_mut() {
348    let mut val = Fragile::new(true);
349    *val.get_mut() = false;
350    assert_eq!(val.to_string(), "false");
351    assert_eq!(val.get(), &false);
352}
353
354#[test]
355#[should_panic]
356fn test_access_other_thread() {
357    use std::thread;
358    let val = Fragile::new(true);
359    thread::spawn(move || {
360        val.get();
361    })
362    .join()
363    .unwrap();
364}
365
366#[test]
367fn test_noop_drop_elsewhere() {
368    use std::thread;
369    let val = Fragile::new(true);
370    thread::spawn(move || {
371        // force the move
372        val.try_get().ok();
373    })
374    .join()
375    .unwrap();
376}
377
378#[test]
379fn test_panic_on_drop_elsewhere() {
380    use std::sync::atomic::{AtomicBool, Ordering};
381    use std::sync::Arc;
382    use std::thread;
383    let was_called = Arc::new(AtomicBool::new(false));
384    struct X(Arc<AtomicBool>);
385    impl Drop for X {
386        fn drop(&mut self) {
387            self.0.store(true, Ordering::SeqCst);
388        }
389    }
390    let val = Fragile::new(X(was_called.clone()));
391    assert!(thread::spawn(move || {
392        val.try_get().ok();
393    })
394    .join()
395    .is_err());
396    assert!(!was_called.load(Ordering::SeqCst));
397}
398
399#[test]
400fn test_drop_in_tls_destructor() {
401    use std::cell::RefCell;
402    use std::sync::atomic::{AtomicUsize, Ordering};
403    use std::sync::Arc;
404    use std::thread;
405
406    struct X(Arc<AtomicUsize>);
407
408    impl Drop for X {
409        fn drop(&mut self) {
410            self.0.fetch_add(1, Ordering::SeqCst);
411        }
412    }
413
414    thread_local!(static SLOT: RefCell<Option<Fragile<X>>> = RefCell::new(None));
415
416    let drop_count = Arc::new(AtomicUsize::new(0));
417    let thread_drop_count = drop_count.clone();
418
419    // The thread check in the destructor must keep working while the thread
420    // is destroying its thread local storage.
421    thread::spawn(move || {
422        SLOT.with(|slot| *slot.borrow_mut() = Some(Fragile::new(X(thread_drop_count))));
423    })
424    .join()
425    .unwrap();
426
427    assert_eq!(drop_count.load(Ordering::SeqCst), 1);
428}
429
430#[test]
431fn test_rc_sending() {
432    use std::rc::Rc;
433    use std::sync::mpsc::channel;
434    use std::thread;
435
436    let val = Fragile::new(Rc::new(true));
437    let (tx, rx) = channel();
438
439    let thread = thread::spawn(move || {
440        assert!(val.try_get().is_err());
441        let here = val;
442        tx.send(here).unwrap();
443    });
444
445    let rv = rx.recv().unwrap();
446    assert!(**rv.get());
447
448    thread.join().unwrap();
449}