blob: 699dabd11f9e4ef515c73bb8e8c4ee5fa565e738 [file] [log] [blame]
use crate::sync::RwLock;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::{
cell::{Cell, UnsafeCell},
fmt,
marker::PhantomData,
};
pub(crate) struct Local<T> {
// TODO(eliza): this once used a `crossbeam_util::ShardedRwLock`. We may
// eventually wish to replace it with a sharded lock implementation on top
// of our internal `RwLock` wrapper type. If possible, we should profile
// this first to determine if it's necessary.
inner: RwLock<Inner<T>>,
}
type Inner<T> = Vec<Option<UnsafeCell<T>>>;
/// Uniquely identifies a thread.
#[repr(transparent)]
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub(crate) struct Id {
id: usize,
_not_send: PhantomData<UnsafeCell<()>>,
}
// === impl Local ===
impl<T> Local<T> {
pub(crate) fn new() -> Self {
let len = Id::current().as_usize();
// Preallocate up to the current thread ID, so we don't have to inside
// the lock.
let mut data = Vec::with_capacity(len);
data.resize_with(len, || None);
Local {
inner: RwLock::new(data),
}
}
pub(crate) fn with_or_else<O>(
&self,
new: impl FnOnce() -> T,
f: impl FnOnce(&mut T) -> O,
) -> Option<O> {
let i = Id::current().as_usize();
let mut f = Some(f);
self.try_with_index(i, |item| f.take().expect("called twice")(item))
.or_else(move || {
self.new_thread(i, new);
self.try_with_index(i, |item| f.take().expect("called twice")(item))
})
}
fn try_with_index<O>(&self, i: usize, f: impl FnOnce(&mut T) -> O) -> Option<O> {
let lock = try_lock!(self.inner.read(), else return None);
let slot = lock.get(i)?.as_ref()?;
let item = unsafe { &mut *slot.get() };
Some(f(item))
}
#[cold]
fn new_thread(&self, i: usize, new: impl FnOnce() -> T) {
let mut lock = try_lock!(self.inner.write());
let this = &mut *lock;
this.resize_with(i + 1, || None);
this[i] = Some(UnsafeCell::new(new()));
}
}
impl<T: Default> Local<T> {
#[inline]
pub(crate) fn with<O>(&self, f: impl FnOnce(&mut T) -> O) -> Option<O> {
self.with_or_else(T::default, f)
}
}
unsafe impl<T> Sync for Local<T> {}
impl<T: fmt::Debug> fmt::Debug for Local<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let id = Id::current();
self.try_with_index(id.as_usize(), |local| {
f.debug_struct("Local")
.field("thread", &id)
.field("local", &*local)
.finish()
})
.unwrap_or_else(|| {
f.debug_struct("Local")
.field("thread", &id)
.field("local", &format_args!("<uninitialized>"))
.finish()
})
}
}
// === impl Id ===
impl Id {
pub(crate) fn current() -> Self {
thread_local! {
static MY_ID: Cell<Option<Id>> = Cell::new(None);
}
MY_ID
.try_with(|my_id| my_id.get().unwrap_or_else(|| Self::new_thread(my_id)))
.unwrap_or_else(|_| Self::poisoned())
}
pub(crate) fn as_usize(self) -> usize {
self.id
}
#[cold]
fn new_thread(local: &Cell<Option<Id>>) -> Self {
static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
let id = NEXT_ID.fetch_add(1, Ordering::AcqRel);
let tid = Self {
id,
_not_send: PhantomData,
};
local.set(Some(tid));
tid
}
#[cold]
fn poisoned() -> Self {
Self {
id: std::usize::MAX,
_not_send: PhantomData,
}
}
/// Returns true if the local thread ID was accessed while unwinding.
pub(crate) fn is_poisoned(self) -> bool {
self.id == std::usize::MAX
}
}
impl fmt::Debug for Id {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_poisoned() {
f.debug_tuple("Id")
.field(&format_args!("<poisoned>"))
.finish()
} else {
f.debug_tuple("Id").field(&self.id).finish()
}
}
}