use std::cell::UnsafeCell;
use std::error::Error;
use std::fmt;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Mutex;
use lazy_static::lazy_static;
use super::pool;
#[macro_export]
macro_rules! task_local {
() => ();
($(#[$attr:meta])* $vis:vis static $name:ident: $t:ty = $init:expr) => (
$(#[$attr])* $vis static $name: $crate::task::LocalKey<$t> = {
#[inline]
fn __init() -> $t {
$init
}
$crate::task::LocalKey {
__init,
__key: ::std::sync::atomic::AtomicUsize::new(0),
}
};
);
($(#[$attr:meta])* $vis:vis static $name:ident: $t:ty = $init:expr; $($rest:tt)*) => (
$crate::task_local!($(#[$attr])* $vis static $name: $t = $init);
$crate::task_local!($($rest)*);
);
}
#[derive(Debug)]
pub struct LocalKey<T: Send + 'static> {
#[doc(hidden)]
pub __init: fn() -> T,
#[doc(hidden)]
pub __key: AtomicUsize,
}
impl<T: Send + 'static> LocalKey<T> {
pub fn with<F, R>(&'static self, f: F) -> R
where
F: FnOnce(&T) -> R,
{
self.try_with(f)
.expect("`LocalKey::with` called outside the context of a task")
}
pub fn try_with<F, R>(&'static self, f: F) -> Result<R, AccessError>
where
F: FnOnce(&T) -> R,
{
pool::get_task(|task| unsafe {
let key = self.key();
let init = || Box::new((self.__init)()) as Box<dyn Send>;
let map = &task.metadata().local_map;
let value: *const dyn Send = map.get_or_insert(key, init);
f(&*(value as *const T))
})
.ok_or(AccessError { _private: () })
}
#[inline]
fn key(&self) -> usize {
#[cold]
fn init(key: &AtomicUsize) -> usize {
lazy_static! {
static ref COUNTER: Mutex<usize> = Mutex::new(1);
}
let mut counter = COUNTER.lock().unwrap();
let prev = key.compare_and_swap(0, *counter, Ordering::AcqRel);
if prev == 0 {
*counter += 1;
*counter - 1
} else {
prev
}
}
let key = self.__key.load(Ordering::Acquire);
if key == 0 { init(&self.__key) } else { key }
}
}
#[derive(Clone, Copy, Eq, PartialEq)]
pub struct AccessError {
_private: (),
}
impl fmt::Debug for AccessError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("AccessError").finish()
}
}
impl fmt::Display for AccessError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
"already destroyed or called outside the context of a task".fmt(f)
}
}
impl Error for AccessError {}
pub(crate) struct Map {
entries: UnsafeCell<Vec<(usize, Box<dyn Send>)>>,
}
impl Map {
pub fn new() -> Map {
Map {
entries: UnsafeCell::new(Vec::new()),
}
}
#[inline]
pub fn get_or_insert(&self, key: usize, init: impl FnOnce() -> Box<dyn Send>) -> &dyn Send {
let entries = unsafe { &mut *self.entries.get() };
let index = match entries.binary_search_by_key(&key, |e| e.0) {
Ok(i) => i,
Err(i) => {
entries.insert(i, (key, init()));
i
}
};
&*entries[index].1
}
pub fn clear(&self) {
let entries = unsafe { &mut *self.entries.get() };
entries.clear();
}
}