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
10pub 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 if self.is_valid() {
45 unsafe { self.unsafe_drop_value() };
48 }
49 }
50}
51
52impl<T> Sticky<T> {
53 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 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 #[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 #[track_caller]
133 pub fn into_inner(mut self) -> T {
134 self.assert_thread();
135 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 if let Some(entry) = registry::try_remove(self.item_id) {
148 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 unsafe { *Box::from_raw(ptr) }
163 }
164
165 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 #[track_caller]
214 pub fn with<R, F>(&self, f: F) -> R
215 where
216 F: for<'a> FnOnce(&'a T) -> R,
217 {
218 f(unsafe { &*self.value_ptr() })
221 }
222
223 #[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 f(unsafe { &mut *self.value_ptr() })
249 }
250
251 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 Ok(f(unsafe { &*ptr }))
267 }
268
269 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 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
392unsafe impl<T> Sync for Sticky<T> {}
395
396unsafe 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 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 }
521 }
522
523 let s1 = Sticky::new(Wat);
524 let s2 = Sticky::new(Wat);
525
526 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 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 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 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}