#![doc = include_str!("../README.md")]
#![warn(clippy::cargo_common_metadata)]
#![warn(clippy::doc_markdown)]
#![warn(clippy::missing_panics_doc)]
#![warn(clippy::must_use_candidate)]
#![warn(clippy::semicolon_if_nothing_returned)]
#![warn(missing_docs)]
#![warn(rustdoc::missing_crate_level_docs)]
#![cfg_attr(feature = "nightly_thread_id_value", feature(thread_id_value))]
use std::mem::ManuallyDrop;
use std::ops::{Deref, DerefMut};
use std::sync::atomic::{AtomicU64, Ordering};
use std::{cmp, fmt, mem};
pub struct ThreadCell<T> {
data: ManuallyDrop<T>,
thread_id: AtomicU64,
}
const GUARD_BIT: u64 = i64::MAX as u64 + 1;
#[allow(clippy::non_send_fields_in_send_ty)]
unsafe impl<T: Send> Send for ThreadCell<T> {}
unsafe impl<T: Send> Sync for ThreadCell<T> {}
impl<T> ThreadCell<T> {
pub const fn new_disowned(data: T) -> Self {
Self {
data: ManuallyDrop::new(data),
thread_id: AtomicU64::new(0),
}
}
pub fn new_owned(data: T) -> Self {
Self {
data: ManuallyDrop::new(data),
thread_id: AtomicU64::new(current_thread_id()),
}
}
pub fn acquire(&self) {
self.thread_id
.compare_exchange(0, current_thread_id(), Ordering::Acquire, Ordering::Relaxed)
.expect("Thread can not acquire ThreadCell");
}
pub fn try_acquire(&self) -> bool {
if self.is_acquired() {
true
} else {
self.thread_id
.compare_exchange(0, current_thread_id(), Ordering::Acquire, Ordering::Relaxed)
.is_ok()
}
}
pub fn try_acquire_once(&self) -> bool {
self.thread_id
.compare_exchange(0, current_thread_id(), Ordering::Acquire, Ordering::Relaxed)
.is_ok()
}
pub fn acquire_get(&self) -> &T {
if !self.is_owned() {
self.acquire();
}
unsafe { self.get_unchecked() }
}
pub fn try_acquire_get(&self) -> Option<&T> {
if self.try_acquire() {
Some(unsafe { self.get_unchecked() })
} else {
None
}
}
pub fn acquire_get_mut(&mut self) -> &mut T {
if !self.is_owned() {
self.acquire();
}
unsafe { self.get_mut_unchecked() }
}
pub fn try_acquire_get_mut(&mut self) -> Option<&mut T> {
if self.try_acquire() {
Some(unsafe { self.get_mut_unchecked() })
} else {
None
}
}
#[inline]
pub fn acquire_guard(&self) -> Guard<T> {
self.thread_id
.compare_exchange(
0,
current_thread_id() | GUARD_BIT,
Ordering::Acquire,
Ordering::Relaxed,
)
.expect("Thread can not acquire ThreadCell");
Guard(self)
}
#[inline]
#[mutants::skip]
pub fn try_acquire_guard(&self) -> Option<Guard<T>> {
if self
.thread_id
.compare_exchange(
0,
current_thread_id() | GUARD_BIT,
Ordering::Acquire,
Ordering::Relaxed,
)
.is_ok()
{
Some(Guard(self))
} else {
None
}
}
#[inline]
pub fn acquire_guard_mut(&mut self) -> GuardMut<T> {
self.thread_id
.compare_exchange(
0,
current_thread_id() | GUARD_BIT,
Ordering::Acquire,
Ordering::Relaxed,
)
.expect("Thread can not acquire ThreadCell");
GuardMut(self)
}
#[inline]
pub fn try_acquire_guard_mut(&mut self) -> Option<GuardMut<T>> {
if self
.thread_id
.compare_exchange(
0,
current_thread_id() | GUARD_BIT,
Ordering::Acquire,
Ordering::Relaxed,
)
.is_ok()
{
Some(GuardMut(self))
} else {
None
}
}
pub fn with<R, F: FnOnce(&T) -> R>(&self, f: F) -> R {
f(&*self.acquire_guard())
}
pub fn with_mut<R, F: FnOnce(&mut T) -> R>(&mut self, f: F) -> R {
f(&mut *self.acquire_guard_mut())
}
pub fn try_with<R, F: FnOnce(&T) -> R>(&self, f: F) -> Option<R> {
Some(f(&*self.try_acquire_guard()?))
}
pub fn try_with_mut<R, F: FnOnce(&mut T) -> R>(&mut self, f: F) -> Option<R> {
Some(f(&mut *self.try_acquire_guard_mut()?))
}
pub unsafe fn steal(&self) -> &Self {
if !self.is_acquired() {
assert!(
self.thread_id.load(Ordering::Acquire) & GUARD_BIT == 0,
"Can't steal guarded ThreadCell"
);
self.thread_id.store(current_thread_id(), Ordering::SeqCst);
}
self
}
pub fn release(&self) {
self.thread_id
.compare_exchange(current_thread_id(), 0, Ordering::Release, Ordering::Relaxed)
.expect("Thread has no access to ThreadCell");
}
#[mutants::skip]
unsafe fn release_unchecked(&self) {
debug_assert!(self.is_owned());
self.thread_id.store(0, Ordering::Release);
}
pub fn try_release(&self) -> bool {
self.thread_id
.compare_exchange(current_thread_id(), 0, Ordering::Release, Ordering::Relaxed)
.is_ok()
}
#[inline(always)]
pub fn is_owned(&self) -> bool {
self.thread_id.load(Ordering::Relaxed) & !GUARD_BIT == current_thread_id()
}
#[inline(always)]
pub fn is_disowned(&self) -> bool {
self.thread_id.load(Ordering::Acquire) == 0
}
#[inline(always)]
pub fn is_acquired(&self) -> bool {
self.thread_id.load(Ordering::Relaxed) == current_thread_id()
}
#[inline(always)]
pub fn is_guarded(&self) -> bool {
self.thread_id.load(Ordering::Relaxed) == current_thread_id() | GUARD_BIT
}
#[inline]
#[track_caller]
fn assert_owned(&self) {
assert!(self.is_owned(), "Thread has no access to ThreadCell");
}
#[inline]
pub fn into_inner(mut self) -> T {
self.assert_owned();
unsafe { ManuallyDrop::take(&mut self.data) }
}
#[inline]
pub fn get(&self) -> &T {
self.assert_owned();
&self.data
}
#[inline]
pub fn get_mut(&mut self) -> &mut T {
self.assert_owned();
&mut self.data
}
#[inline]
pub fn try_get(&self) -> Option<&T> {
if self.is_owned() {
Some(&self.data)
} else {
None
}
}
#[inline]
pub fn try_get_mut(&mut self) -> Option<&mut T> {
if self.is_owned() {
Some(&mut self.data)
} else {
None
}
}
#[inline]
pub unsafe fn get_unchecked(&self) -> &T {
debug_assert!(self.is_owned(), "Thread has no access to ThreadCell");
&self.data
}
#[inline]
pub unsafe fn get_mut_unchecked(&mut self) -> &mut T {
&mut self.data
}
}
#[mutants::skip]
impl<T> Drop for ThreadCell<T> {
#[cfg(debug_assertions)]
fn drop(&mut self) {
let owner = self.thread_id.load(Ordering::Acquire) & !GUARD_BIT;
if owner == 0 || owner == current_thread_id() {
if mem::needs_drop::<T>() {
unsafe { ManuallyDrop::drop(&mut self.data) };
}
} else {
panic!("Thread has no access to ThreadCell");
}
}
#[cfg(not(debug_assertions))]
fn drop(&mut self) {
if mem::needs_drop::<T>() {
let owner = self.thread_id.load(Ordering::Acquire) & !GUARD_BIT;
if owner == 0 || owner == current_thread_id() {
unsafe { ManuallyDrop::drop(&mut self.data) };
} else {
panic!("Thread has no access to ThreadCell");
}
}
}
}
impl<T> From<T> for ThreadCell<T> {
#[inline]
fn from(t: T) -> ThreadCell<T> {
ThreadCell::new_owned(t)
}
}
impl<T: Clone> Clone for ThreadCell<T> {
#[inline]
fn clone(&self) -> ThreadCell<T> {
ThreadCell::new_owned(self.get().clone())
}
}
impl<T: Default> Default for ThreadCell<T> {
#[inline]
fn default() -> ThreadCell<T> {
ThreadCell::new_owned(T::default())
}
}
#[mutants::skip]
impl<T: PartialEq> PartialEq for ThreadCell<T> {
#[inline]
fn eq(&self, other: &ThreadCell<T>) -> bool {
*self.get() == *other.get()
}
}
impl<T: Eq> Eq for ThreadCell<T> {}
#[mutants::skip]
impl<T: PartialOrd> PartialOrd for ThreadCell<T> {
#[inline]
fn partial_cmp(&self, other: &ThreadCell<T>) -> Option<cmp::Ordering> {
self.get().partial_cmp(other.get())
}
#[inline]
fn lt(&self, other: &ThreadCell<T>) -> bool {
*self.get() < *other.get()
}
#[inline]
fn le(&self, other: &ThreadCell<T>) -> bool {
*self.get() <= *other.get()
}
#[inline]
fn gt(&self, other: &ThreadCell<T>) -> bool {
*self.get() > *other.get()
}
#[inline]
fn ge(&self, other: &ThreadCell<T>) -> bool {
*self.get() >= *other.get()
}
}
#[mutants::skip]
impl<T: Ord> Ord for ThreadCell<T> {
#[inline]
fn cmp(&self, other: &ThreadCell<T>) -> cmp::Ordering {
self.get().cmp(other.get())
}
}
#[mutants::skip]
impl<T: fmt::Display> fmt::Display for ThreadCell<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
fmt::Display::fmt(self.get(), f)
}
}
#[allow(clippy::doc_markdown)]
#[mutants::skip]
impl<T: fmt::Debug> fmt::Debug for ThreadCell<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match self.try_get() {
Some(data) => f.debug_struct("ThreadCell").field("data", data).finish(),
None => f.write_str("<ThreadCell>"),
}
}
}
#[cfg(not(feature = "nightly_thread_id_value"))]
use std::num::NonZeroU64;
#[cfg(not(feature = "nightly_thread_id_value"))]
struct ThreadId(NonZeroU64);
#[cfg(not(feature = "nightly_thread_id_value"))]
impl ThreadId {
#[inline]
#[must_use]
#[mutants::skip]
fn current() -> ThreadId {
thread_local!(static THREAD_ID: NonZeroU64 = {
static COUNTER: AtomicU64 = AtomicU64::new(1);
{
let id = NonZeroU64::new(COUNTER.fetch_add(1, Ordering::Relaxed)).unwrap();
assert!(id.get() <= i64::MAX as u64, "more than i64::MAX threads");
id
}
});
THREAD_ID.with(|&x| ThreadId(x))
}
#[inline(always)]
#[must_use]
#[mutants::skip]
fn as_u64(&self) -> NonZeroU64 {
self.0
}
}
#[test]
#[cfg(not(feature = "nightly_thread_id_value"))]
fn threadid() {
let main = ThreadId::current().as_u64().get();
let child = std::thread::spawn(|| ThreadId::current().as_u64().get())
.join()
.unwrap();
println!("{main}, {child}");
assert_ne!(main, 0);
assert_ne!(main, child);
}
#[cfg(not(feature = "nightly_thread_id_value"))]
#[mutants::skip]
#[inline]
fn current_thread_id() -> u64 {
ThreadId::current().as_u64().get()
}
#[cfg(feature = "nightly_thread_id_value")]
#[mutants::skip]
#[inline]
fn current_thread_id() -> u64 {
std::thread::current().id().as_u64().get()
}
#[repr(transparent)]
pub struct Guard<'a, T>(&'a ThreadCell<T>);
impl<T> Drop for Guard<'_, T> {
#[mutants::skip]
fn drop(&mut self) {
unsafe {
self.0.release_unchecked();
}
}
}
impl<T> Deref for Guard<'_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.0.get()
}
}
#[repr(transparent)]
pub struct GuardMut<'a, T>(&'a mut ThreadCell<T>);
impl<T> Drop for GuardMut<'_, T> {
fn drop(&mut self) {
unsafe {
self.0.release_unchecked();
}
}
}
impl<T> Deref for GuardMut<'_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.0.get()
}
}
impl<T> DerefMut for GuardMut<'_, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.0.get_mut()
}
}