use std::cell::UnsafeCell;
use std::fmt;
use std::marker::PhantomData;
use std::panic::{self, AssertUnwindSafe};
use crate::AtomicCounter;
use crate::primitives::Arc;
use crate::primitives::{AtomicBool, Ordering};
type ErasedFn = Box<dyn FnOnce() + Send + 'static>;
pub struct TaskScope<'scope, 'env: 'scope, D> {
pending: Arc<AtomicCounter>,
data: D,
scope: PhantomData<&'scope mut &'scope ()>,
env: PhantomData<&'env mut &'env ()>,
}
pub struct StackSlot<F> {
claimed: AtomicBool,
f: UnsafeCell<Option<F>>,
}
unsafe impl<F: Send> Sync for StackSlot<F> {}
impl<F> StackSlot<F> {
#[must_use]
pub fn new(f: F) -> Self {
Self {
claimed: AtomicBool::new(false),
f: UnsafeCell::new(Some(f)),
}
}
}
impl<F> fmt::Debug for StackSlot<F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("StackSlot")
.field("claimed", &self.claimed.load(Ordering::Relaxed))
.finish_non_exhaustive()
}
}
pub struct ScopedTask {
body: TaskBody,
_done: DoneGuard,
}
enum TaskBody {
Boxed(Option<ErasedFn>),
Slot {
slot: *const (),
call: unsafe fn(*const (), bool),
pending: bool,
},
}
impl Drop for TaskBody {
fn drop(&mut self) {
if let Self::Slot {
slot,
call,
pending,
} = self
&& *pending
{
*pending = false;
unsafe { call(*slot, false) };
}
}
}
unsafe fn call_slot<F: FnOnce()>(ptr: *const (), run: bool) {
let slot = unsafe { &*ptr.cast::<StackSlot<F>>() };
let f = unsafe { (*slot.f.get()).take() };
if run {
if let Some(f) = f {
f();
}
} else {
drop(f);
}
}
unsafe impl Send for ScopedTask {}
struct DoneGuard(Arc<AtomicCounter>);
impl Drop for DoneGuard {
fn drop(&mut self) {
self.0.decrement();
}
}
impl ScopedTask {
pub fn run(mut self) {
match &mut self.body {
TaskBody::Boxed(f) => {
if let Some(f) = f.take() {
f();
}
}
TaskBody::Slot {
slot,
call,
pending,
} => {
if *pending {
*pending = false;
unsafe { call(*slot, true) };
}
}
}
}
}
impl fmt::Debug for ScopedTask {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let pending = match &self.body {
TaskBody::Boxed(f) => f.is_some(),
TaskBody::Slot { pending, .. } => *pending,
};
f.debug_struct("ScopedTask")
.field("pending", &pending)
.finish_non_exhaustive()
}
}
impl<D: fmt::Debug> fmt::Debug for TaskScope<'_, '_, D> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TaskScope")
.field("pending", &self.pending.get())
.field("data", &self.data)
.finish()
}
}
impl<'scope, D> TaskScope<'scope, '_, D> {
#[must_use]
pub fn data(&self) -> &D {
&self.data
}
#[must_use]
pub fn pending(&self) -> usize {
self.pending.get()
}
fn register(&self) -> DoneGuard {
if self.pending.increment().is_err() {
std::process::abort();
}
DoneGuard(Arc::clone(&self.pending))
}
pub fn task<F>(&self, f: F) -> ScopedTask
where
F: FnOnce() + Send + 'scope,
{
let done = self.register();
let boxed: Box<dyn FnOnce() + Send + 'scope> = Box::new(f);
let erased: ErasedFn =
unsafe { std::mem::transmute::<Box<dyn FnOnce() + Send + 'scope>, ErasedFn>(boxed) };
ScopedTask {
body: TaskBody::Boxed(Some(erased)),
_done: done,
}
}
pub fn task_in<F>(&self, slot: &'scope StackSlot<F>) -> Option<ScopedTask>
where
F: FnOnce() + Send + 'scope,
{
if slot.claimed.swap(true, Ordering::AcqRel) {
return None;
}
let done = self.register();
Some(ScopedTask {
body: TaskBody::Slot {
slot: std::ptr::from_ref(slot).cast::<()>(),
call: call_slot::<F>,
pending: true,
},
_done: done,
})
}
}
#[cfg(not(loom))]
mod counter_cache {
use std::cell::RefCell;
use crate::AtomicCounter;
use crate::primitives::Arc;
const CAPACITY: usize = 64;
thread_local! {
static SPARE: RefCell<Vec<Arc<AtomicCounter>>> = const { RefCell::new(Vec::new()) };
}
pub(super) fn take() -> Arc<AtomicCounter> {
SPARE
.try_with(|spare| spare.try_borrow_mut().ok().and_then(|mut s| s.pop()))
.ok()
.flatten()
.unwrap_or_else(|| Arc::new(AtomicCounter::new(0)))
}
pub(super) fn give_back(counter: &Arc<AtomicCounter>) {
if !counter.is_zero() {
return;
}
let counter = Arc::clone(counter);
let _ = SPARE.try_with(|spare| {
if let Ok(mut spare) = spare.try_borrow_mut()
&& spare.len() < CAPACITY
{
spare.push(counter);
}
});
}
}
#[cfg(loom)]
mod counter_cache {
use crate::AtomicCounter;
use crate::primitives::Arc;
pub(super) fn take() -> Arc<AtomicCounter> {
Arc::new(AtomicCounter::new(0))
}
pub(super) fn give_back(_counter: &Arc<AtomicCounter>) {}
}
pub fn scope<'env, D, T>(
data: D,
body: impl for<'scope> FnOnce(&'scope TaskScope<'scope, 'env, D>) -> T,
mut help: impl FnMut() -> bool,
) -> T {
let scope = TaskScope {
pending: counter_cache::take(),
data,
scope: PhantomData,
env: PhantomData,
};
let result = panic::catch_unwind(AssertUnwindSafe(|| body(&scope)));
let waited = panic::catch_unwind(AssertUnwindSafe(|| {
scope.pending.wait_while_helping(&mut help)
}));
if waited.is_err() {
std::process::abort();
}
counter_cache::give_back(&scope.pending);
match result {
Ok(value) => value,
Err(payload) => panic::resume_unwind(payload),
}
}
#[cfg(all(test, not(loom)))]
mod tests {
use super::*;
use std::sync::Mutex;
use std::sync::atomic::{AtomicUsize, Ordering};
#[test]
fn tasks_borrow_stack_data_across_threads() {
let mut data = vec![0_u64; 64];
let (tx, rx) = std::sync::mpsc::channel::<ScopedTask>();
let rx = Mutex::new(rx);
std::thread::scope(|ts| {
for _ in 0..4 {
ts.spawn(|| {
while let Ok(task) = rx.lock().unwrap().recv() {
task.run();
}
});
}
scope(
(),
|s| {
for (i, chunk) in data.chunks_mut(8).enumerate() {
let task = s.task(move || {
for x in chunk.iter_mut() {
*x = i as u64;
}
});
tx.send(task).unwrap();
}
},
|| false,
);
drop(tx);
});
for (i, chunk) in data.chunks(8).enumerate() {
assert!(chunk.iter().all(|&x| x == i as u64));
}
}
#[test]
fn dropped_task_counts_as_finished() {
let ran = AtomicUsize::new(0);
scope(
(),
|s| {
let task = s.task(|| {
ran.fetch_add(1, Ordering::SeqCst);
});
assert_eq!(s.pending(), 1);
drop(task);
assert_eq!(s.pending(), 0);
},
|| false,
);
assert_eq!(ran.load(Ordering::SeqCst), 0);
}
#[test]
fn help_runs_pending_tasks() {
let queue: Mutex<Vec<ScopedTask>> = Mutex::new(Vec::new());
let sum = AtomicUsize::new(0);
let extra = scope(
7_usize,
|s| {
for i in 0..10 {
let sum = ∑
let extra = *s.data();
queue.lock().unwrap().push(s.task(move || {
sum.fetch_add(i + extra, Ordering::SeqCst);
}));
}
*s.data()
},
|| match queue.lock().unwrap().pop() {
Some(task) => {
task.run();
true
}
None => false,
},
);
assert_eq!(extra, 7);
assert_eq!(sum.load(Ordering::SeqCst), 45 + 70);
}
#[test]
fn body_panic_waits_for_tasks_then_propagates() {
let queue: Mutex<Vec<ScopedTask>> = Mutex::new(Vec::new());
let ran = AtomicUsize::new(0);
let result = panic::catch_unwind(AssertUnwindSafe(|| {
scope(
(),
|s| {
queue.lock().unwrap().push(s.task(|| {
ran.fetch_add(1, Ordering::SeqCst);
}));
panic!("body failed");
},
|| match queue.lock().unwrap().pop() {
Some(task) => {
task.run();
true
}
None => false,
},
)
}));
assert!(result.is_err());
assert_eq!(
ran.load(Ordering::SeqCst),
1,
"task finished before unwinding"
);
}
#[test]
fn stack_slot_task_runs_on_other_thread() {
let mut out = 0_u64;
let slot = StackSlot::new(|| out = 42);
std::thread::scope(|ts| {
scope(
(),
|s| {
let task = s.task_in(&slot).unwrap();
assert!(s.task_in(&slot).is_none(), "slot is handed out only once");
ts.spawn(move || task.run());
},
|| false,
);
});
let _ = slot;
assert_eq!(out, 42);
}
#[test]
fn stack_slot_dropped_task_drops_closure_once() {
let tracker = Arc::new(());
let captured = Arc::clone(&tracker);
let slot = StackSlot::new(move || drop(captured));
scope(
(),
|s| {
let task = s.task_in(&slot).unwrap();
assert_eq!(s.pending(), 1);
drop(task);
assert_eq!(s.pending(), 0);
},
|| false,
);
assert_eq!(
Arc::strong_count(&tracker),
1,
"closure dropped without running"
);
drop(slot);
}
#[test]
fn stack_slot_panic_signals_completion() {
let slot = StackSlot::new(|| panic!("slot task failed"));
scope(
(),
|s| {
let task = s.task_in(&slot).unwrap();
assert!(panic::catch_unwind(AssertUnwindSafe(|| task.run())).is_err());
assert_eq!(s.pending(), 0);
},
|| false,
);
}
#[test]
fn counters_are_reused_between_scopes() {
for _ in 0..3 {
scope(
(),
|s| {
let task = s.task(|| {});
task.run();
},
|| false,
);
}
let reused = counter_cache::take();
assert!(reused.is_zero());
assert_eq!(Arc::strong_count(&reused), 1);
}
#[test]
fn task_panic_still_signals_completion() {
scope(
(),
|s| {
let task = s.task(|| panic!("task failed"));
let r = panic::catch_unwind(AssertUnwindSafe(|| task.run()));
assert!(r.is_err());
assert_eq!(s.pending(), 0);
},
|| false,
);
}
}