use std::any::Any;
use std::cell::{Cell, RefCell};
use std::future::Future;
use std::panic::{AssertUnwindSafe, catch_unwind, resume_unwind};
use std::pin::{Pin, pin};
use std::rc::Rc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Condvar, Mutex, MutexGuard, PoisonError};
use std::task::{Context, Poll, Wake, Waker};
use std::thread::ThreadId;
use std::time::{Duration, Instant};
use crate::cx::Cx;
use crate::runtime::io_driver::IoDriverHandle;
use crate::runtime::local::ScopedLocalStoreKey;
use crate::runtime::scheduler::three_lane::{ScopedWorkerId, ThreeLaneScheduler, ThreeLaneWorker};
use crate::runtime::scheduler::worker::Parker;
use crate::runtime::spawn_mailbox::{self, ScopedLocalSpawnLaneOwner, SpawnGateway, SpawnMailbox};
use crate::runtime::state::SpawnError;
use crate::runtime::task_handle::TaskHandle;
const BACKGROUND_WAIT_SLICE: Duration = Duration::from_millis(100);
const SHUTDOWN_ROOT_PARK_SLICE: Duration = Duration::from_millis(1);
const DRAIN_IDLE_SLICE: Duration = Duration::from_millis(1);
pub const POST_ROOT_DRAIN_TURNS: u32 = 64;
const DRAIN_DEADLINE_CHECK_TURNS: u32 = 16;
type ErasedWorker = Box<dyn Any + Send>;
fn erase_worker(worker: Box<ThreeLaneWorker>) -> ErasedWorker {
worker
}
fn recover_worker(erased: ErasedWorker) -> Box<ThreeLaneWorker> {
erased
.downcast::<ThreeLaneWorker>()
.expect("worker slots only ever hold a ThreeLaneWorker")
}
enum WorkerSlot {
Background,
Requested(ThreadId),
Offered(ThreadId, ErasedWorker),
Loaned,
Returned(ErasedWorker),
Closed,
}
pub struct CurrentThreadDriver {
slot: Mutex<WorkerSlot>,
changed: Condvar,
handover_requested: AtomicBool,
background_thread: Mutex<Option<ThreadId>>,
reentrant: Mutex<Option<(ThreadId, ErasedWorker)>>,
gateway: Option<Arc<SpawnGateway>>,
store_key: usize,
}
impl CurrentThreadDriver {
#[cfg(any(test, not(target_arch = "wasm32")))]
pub fn new(gateway: Option<Arc<SpawnGateway>>, store_key: usize) -> Self {
Self {
slot: Mutex::new(WorkerSlot::Background),
changed: Condvar::new(),
handover_requested: AtomicBool::new(false),
background_thread: Mutex::new(None),
reentrant: Mutex::new(None),
gateway,
store_key,
}
}
fn lock_slot(&self) -> MutexGuard<'_, WorkerSlot> {
self.slot.lock().unwrap_or_else(PoisonError::into_inner)
}
fn on_background_thread(&self) -> bool {
self.background_thread
.lock()
.unwrap_or_else(PoisonError::into_inner)
.is_some_and(|id| id == std::thread::current().id())
}
fn worker_active_on_this_thread(&self) -> bool {
self.gateway
.as_deref()
.is_some_and(spawn_mailbox::local_spawn_lane_is_owned_by)
}
pub fn shutdown(&self) {
let retired = {
let mut slot = self.lock_slot();
let retired = std::mem::replace(&mut *slot, WorkerSlot::Closed);
self.changed.notify_all();
retired
};
drop(retired);
crate::runtime::local::retire_local_store(self.store_key);
}
pub fn run_background(&self, worker: ThreeLaneWorker, on_start: impl FnOnce()) {
let _close_on_exit = BackgroundExit(self);
*self
.background_thread
.lock()
.unwrap_or_else(PoisonError::into_inner) = Some(std::thread::current().id());
let shutdown = Arc::clone(&worker.shutdown);
let _store_key = ScopedLocalStoreKey::new(worker.local_store_key());
let _lane_owner = worker
.spawn_mailbox
.as_ref()
.map(|mailbox| ScopedLocalSpawnLaneOwner::new(Arc::clone(mailbox)));
on_start();
let mut worker = Box::new(worker);
loop {
worker.run_loop_until(
&mut || self.handover_requested.load(Ordering::Acquire),
false,
);
if shutdown.load(Ordering::Acquire) {
self.shutdown();
return;
}
let can_offer = crate::runtime::local::local_task_count() == 0
&& spawn_mailbox::local_spawn_lane_is_empty();
let mut slot = self.lock_slot();
match *slot {
WorkerSlot::Requested(owner) if can_offer => {
*slot = WorkerSlot::Offered(owner, erase_worker(worker));
self.changed.notify_all();
}
WorkerSlot::Requested(_) => {
*slot = WorkerSlot::Background;
self.handover_requested.store(false, Ordering::Release);
self.changed.notify_all();
continue;
}
WorkerSlot::Closed => return,
_ => {
self.handover_requested.store(false, Ordering::Release);
continue;
}
}
loop {
match std::mem::replace(&mut *slot, WorkerSlot::Loaned) {
WorkerSlot::Returned(returned) => {
*slot = WorkerSlot::Background;
worker = recover_worker(returned);
self.changed.notify_all();
break;
}
WorkerSlot::Closed => {
*slot = WorkerSlot::Closed;
return;
}
other => *slot = other,
}
if shutdown.load(Ordering::Acquire) {
drop(slot);
self.shutdown();
return;
}
let (guard, _) = self
.changed
.wait_timeout(slot, BACKGROUND_WAIT_SLICE)
.unwrap_or_else(PoisonError::into_inner);
slot = guard;
}
drop(slot);
}
}
fn acquire(&self, scheduler: &ThreeLaneScheduler) -> Option<Box<ThreeLaneWorker>> {
if self.on_background_thread() {
return None;
}
let requester = std::thread::current().id();
let mut slot = self.lock_slot();
match std::mem::replace(&mut *slot, WorkerSlot::Loaned) {
WorkerSlot::Returned(worker) => return Some(recover_worker(worker)),
WorkerSlot::Background => *slot = WorkerSlot::Requested(requester),
other => {
*slot = other;
return None;
}
}
self.handover_requested.store(true, Ordering::Release);
scheduler.wake_all();
loop {
match std::mem::replace(&mut *slot, WorkerSlot::Loaned) {
WorkerSlot::Offered(owner, worker) if owner == requester => {
self.handover_requested.store(false, Ordering::Release);
return Some(recover_worker(worker));
}
WorkerSlot::Requested(owner) if owner == requester => {
*slot = WorkerSlot::Requested(owner);
}
WorkerSlot::Closed => {
*slot = WorkerSlot::Closed;
self.handover_requested.store(false, Ordering::Release);
return None;
}
other => {
*slot = other;
return None;
}
}
slot = self
.changed
.wait(slot)
.unwrap_or_else(PoisonError::into_inner);
}
}
fn release(&self, worker: Box<ThreeLaneWorker>) {
let retired = {
let mut slot = self.lock_slot();
if matches!(*slot, WorkerSlot::Closed) {
Some(worker)
} else {
*slot = WorkerSlot::Returned(erase_worker(worker));
None
}
};
self.changed.notify_all();
drop(retired);
}
fn park_reentrant(&self, worker: Box<ThreeLaneWorker>) {
let mut parked = self
.reentrant
.lock()
.unwrap_or_else(PoisonError::into_inner);
debug_assert!(parked.is_none(), "re-entrancy slot already holds a worker");
*parked = Some((std::thread::current().id(), erase_worker(worker)));
}
fn take_reentrant(&self) -> Option<Box<ThreeLaneWorker>> {
let mut parked = self
.reentrant
.lock()
.unwrap_or_else(PoisonError::into_inner);
let owned_here = parked
.as_ref()
.is_some_and(|(owner, _)| *owner == std::thread::current().id());
if !owned_here {
return None;
}
parked.take().map(|(_, worker)| recover_worker(worker))
}
fn borrow_for_drive(&self, scheduler: &ThreeLaneScheduler) -> Option<WorkerLoan<'_>> {
if self.worker_active_on_this_thread() {
let worker = self.take_reentrant()?;
return Some(WorkerLoan {
driver: self,
worker: Some(worker),
return_to: LoanReturn::Reentrant,
});
}
let worker = self.acquire(scheduler)?;
Some(WorkerLoan {
driver: self,
worker: Some(worker),
return_to: LoanReturn::Background,
})
}
pub fn drive<F: Future>(
&self,
scheduler: &ThreeLaneScheduler,
request_cx: &Cx,
future: F,
) -> Result<F::Output, F> {
let Some(mut loan) = self.borrow_for_drive(scheduler) else {
return Err(future);
};
let outcome = loan.with_thread_context(|driver, worker| {
drive_root_on(driver, worker, request_cx, future)
});
drop(loan);
match outcome {
Ok(output) => Ok(output),
Err(payload) => resume_unwind(payload),
}
}
pub fn drain_until(
&self,
scheduler: &ThreeLaneScheduler,
started: Instant,
bound: Duration,
drained: &mut dyn FnMut() -> bool,
) -> Option<bool> {
if self.worker_active_on_this_thread() {
return None;
}
let worker = self.acquire(scheduler)?;
let mut loan = WorkerLoan {
driver: self,
worker: Some(worker),
return_to: LoanReturn::Background,
};
Some(loan.with_thread_context(|_, worker| {
let worker = worker
.as_mut()
.expect("worker stays on loan for the whole drain");
loop {
let mut turns = 0_u32;
worker.run_loop_until(
&mut || {
turns = turns.wrapping_add(1);
turns % DRAIN_DEADLINE_CHECK_TURNS == 0 && started.elapsed() >= bound
},
true,
);
if drained() {
return true;
}
if started.elapsed() >= bound {
return false;
}
std::thread::sleep(DRAIN_IDLE_SLICE);
}
}))
}
}
struct BackgroundExit<'a>(&'a CurrentThreadDriver);
impl Drop for BackgroundExit<'_> {
fn drop(&mut self) {
self.0.shutdown();
}
}
enum LoanReturn {
Background,
Reentrant,
}
struct WorkerLoan<'a> {
driver: &'a CurrentThreadDriver,
worker: Option<Box<ThreeLaneWorker>>,
return_to: LoanReturn,
}
impl WorkerLoan<'_> {
fn with_thread_context<R>(
&mut self,
f: impl FnOnce(&CurrentThreadDriver, &mut Option<Box<ThreeLaneWorker>>) -> R,
) -> R {
let (worker_id, mailbox, store_key): (usize, Option<Arc<SpawnMailbox>>, usize) = {
let worker = self
.worker
.as_ref()
.expect("worker stays on loan for the whole drive");
(
worker.id,
worker.spawn_mailbox.clone(),
worker.local_store_key(),
)
};
let _store_key = ScopedLocalStoreKey::new(store_key);
let _worker_id = ScopedWorkerId::new(worker_id);
let _lane_owner = mailbox.map(ScopedLocalSpawnLaneOwner::new);
let isolate_lane = !matches!(self.return_to, LoanReturn::Reentrant);
let outer_requests = if isolate_lane {
spawn_mailbox::take_unowned_local_spawn_lane()
} else {
Default::default()
};
let result = f(self.driver, &mut self.worker);
if isolate_lane {
let mut orphaned = Vec::new();
spawn_mailbox::drain_local_spawn_lane(usize::MAX, &mut orphaned);
for request in orphaned {
request.resolve_failed(SpawnError::RuntimeUnavailable);
}
spawn_mailbox::restore_local_spawn_lane(outer_requests);
}
result
}
}
impl Drop for WorkerLoan<'_> {
fn drop(&mut self) {
if let Some(worker) = self.worker.take() {
match self.return_to {
LoanReturn::Background => self.driver.release(worker),
LoanReturn::Reentrant => self.driver.park_reentrant(worker),
}
}
}
}
fn drive_root_on<F: Future>(
driver: &CurrentThreadDriver,
slot: &mut Option<Box<ThreeLaneWorker>>,
request_cx: &Cx,
future: F,
) -> std::thread::Result<F::Output> {
let root_waker = {
let worker = slot
.as_ref()
.expect("worker stays on loan for the whole drive");
Arc::new(RootWaker {
woken: AtomicBool::new(true),
parker: worker.parker.clone(),
io: worker.io_driver.clone(),
})
};
let waker = Waker::from(Arc::clone(&root_waker));
let mut ctx = Context::from_waker(&waker);
let mut future = pin!(Some(future));
let registration = RootRegistration::spawn(request_cx);
if let Ok(task) = registration.as_ref() {
loaned(slot).run_loop_until(&mut || task.cx_available() || task.is_finished(), false);
}
let root_cx = registration
.as_ref()
.ok()
.and_then(RootRegistration::take_cx)
.unwrap_or_else(|| request_cx.clone());
let root_cx_guard = Cx::set_current(Some(root_cx));
let result = loop {
if loaned(slot).shutdown.load(Ordering::Acquire) {
break drive_root_after_shutdown(
loaned(slot),
&root_waker,
&mut ctx,
future.as_mut().as_pin_mut().expect("root is still live"),
);
}
if root_waker.take_woken() {
let parked = slot
.take()
.expect("worker stays on loan for the whole drive");
driver.park_reentrant(parked);
let polled = catch_unwind(AssertUnwindSafe(|| {
future
.as_mut()
.as_pin_mut()
.expect("root is still live")
.poll(&mut ctx)
}));
*slot = Some(
driver
.take_reentrant()
.expect("a nested drive hands the worker back before the root poll returns"),
);
match polled {
Ok(Poll::Ready(output)) => break Ok(output),
Ok(Poll::Pending) => {}
Err(payload) => break Err(payload),
}
loaned(slot).run_once();
} else {
loaned(slot).run_loop_until(&mut || root_waker.is_woken(), false);
}
};
driver.park_reentrant(slot.take().expect("worker stays on loan through root drop"));
let dropped = catch_unwind(AssertUnwindSafe(|| future.set(None)));
*slot = Some(
driver
.take_reentrant()
.expect("a nested drive hands the worker back before root drop returns"),
);
let result = result.and_then(|output| dropped.map(|()| output));
drop(root_cx_guard);
if let Ok(task) = registration.as_ref() {
task.finish_now(loaned(slot));
}
let mut turns = 0_u32;
loaned(slot).run_loop_until(
&mut || {
turns = turns.saturating_add(1);
turns > POST_ROOT_DRAIN_TURNS
},
true,
);
result
}
fn loaned(slot: &mut Option<Box<ThreeLaneWorker>>) -> &mut ThreeLaneWorker {
slot.as_deref_mut()
.expect("worker stays on loan for the whole drive")
}
fn drive_root_after_shutdown<F: Future>(
worker: &ThreeLaneWorker,
root_waker: &Arc<RootWaker>,
ctx: &mut Context<'_>,
mut future: Pin<&mut F>,
) -> std::thread::Result<F::Output> {
loop {
if let Some(timer) = worker.timer_driver.as_ref() {
let _ = timer.process_timers();
}
if root_waker.take_woken() {
match catch_unwind(AssertUnwindSafe(|| future.as_mut().poll(ctx))) {
Ok(Poll::Ready(output)) => return Ok(output),
Ok(Poll::Pending) => {}
Err(payload) => return Err(payload),
}
}
worker.parker.park_timeout(SHUTDOWN_ROOT_PARK_SLICE);
}
}
struct RootWaker {
woken: AtomicBool,
parker: Parker,
io: Option<IoDriverHandle>,
}
impl RootWaker {
fn take_woken(&self) -> bool {
self.woken.swap(false, Ordering::AcqRel)
}
fn is_woken(&self) -> bool {
self.woken.load(Ordering::Acquire)
}
}
impl Wake for RootWaker {
fn wake(self: Arc<Self>) {
Self::wake_by_ref(&self);
}
fn wake_by_ref(self: &Arc<Self>) {
if self.woken.swap(true, Ordering::AcqRel) {
return;
}
self.parker.unpark();
if let Some(io) = self.io.as_ref() {
let _ = io.wake();
}
}
}
struct RootRegistrationShared {
cx: RefCell<Option<Cx>>,
done: Cell<bool>,
}
struct RootRegistration {
shared: Rc<RootRegistrationShared>,
handle: TaskHandle<()>,
}
impl RootRegistration {
fn spawn(parent: &Cx) -> Result<Self, SpawnError> {
let shared = Rc::new(RootRegistrationShared {
cx: RefCell::new(None),
done: Cell::new(false),
});
let task_shared = Rc::clone(&shared);
let handle = parent.spawn_local(move |cx: Cx| async move {
*task_shared.cx.borrow_mut() = Some(cx);
std::future::poll_fn(|_| {
if task_shared.done.get() {
Poll::Ready(())
} else {
Poll::Pending
}
})
.await;
})?;
Ok(Self { shared, handle })
}
fn cx_available(&self) -> bool {
self.shared.cx.borrow().is_some()
}
fn take_cx(&self) -> Option<Cx> {
self.shared.cx.borrow_mut().take()
}
fn is_finished(&self) -> bool {
self.handle.is_finished()
}
fn finish_now(&self, worker: &mut ThreeLaneWorker) {
self.shared.done.set(true);
if !self.is_finished() {
worker.execute(self.handle.task_id());
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::evidence_sink::{CollectorSink, EvidenceSink};
#[derive(Debug)]
struct ReentrantWorkerSink {
driver: std::sync::Weak<CurrentThreadDriver>,
drop_status: Arc<std::sync::atomic::AtomicUsize>,
entries: CollectorSink,
}
impl EvidenceSink for ReentrantWorkerSink {
fn emit(&self, entry: &franken_evidence::EvidenceLedger) {
self.entries.emit(entry);
}
fn next_evidence_ts(&self) -> u64 {
self.entries.next_evidence_ts()
}
}
impl Drop for ReentrantWorkerSink {
fn drop(&mut self) {
if let Some(driver) = self.driver.upgrade() {
let unlocked = driver.slot.try_lock().is_ok();
if unlocked {
driver.shutdown();
self.drop_status.store(1, Ordering::SeqCst);
} else {
self.drop_status.store(2, Ordering::SeqCst);
}
}
}
}
#[derive(Debug, Clone, Copy)]
enum WorkerRetirement {
Offered,
Returned,
ClosedLoanReturn,
BackgroundWaiting,
}
fn assert_worker_drop_can_reenter(retirement: WorkerRetirement) {
use crate::runtime::RuntimeState;
use crate::sync::ContendedMutex;
use std::sync::atomic::AtomicUsize;
let state = Arc::new(ContendedMutex::new(
"worker_retirement",
RuntimeState::new(),
));
let mut scheduler = ThreeLaneScheduler::new(1, &state);
let driver = Arc::new(CurrentThreadDriver::new(None, scheduler.local_store_key()));
let drop_status = Arc::new(AtomicUsize::new(0));
let mut worker = Box::new(scheduler.take_workers().pop().expect("one real worker"));
worker.set_evidence_sink(Arc::new(ReentrantWorkerSink {
driver: Arc::downgrade(&driver),
drop_status: Arc::clone(&drop_status),
entries: CollectorSink::new(),
}));
match retirement {
WorkerRetirement::Offered => {
*driver.lock_slot() =
WorkerSlot::Offered(std::thread::current().id(), erase_worker(worker));
driver.shutdown();
}
WorkerRetirement::Returned => {
*driver.lock_slot() = WorkerSlot::Returned(erase_worker(worker));
driver.shutdown();
}
WorkerRetirement::ClosedLoanReturn => {
driver.shutdown();
driver.release(worker);
}
WorkerRetirement::BackgroundWaiting => {
*driver.lock_slot() = WorkerSlot::Requested(std::thread::current().id());
driver.handover_requested.store(true, Ordering::Release);
let shutdown = Arc::clone(&worker.shutdown);
let observer_driver = Arc::clone(&driver);
let observer = std::thread::spawn(move || {
let (slot, _) = observer_driver
.changed
.wait_timeout_while(
observer_driver.lock_slot(),
Duration::from_secs(5),
|slot| matches!(slot, WorkerSlot::Requested(_)),
)
.unwrap_or_else(PoisonError::into_inner);
let offered = matches!(*slot, WorkerSlot::Offered(_, _));
shutdown.store(true, Ordering::Release);
observer_driver.changed.notify_all();
offered
});
driver.run_background(*worker, || {});
assert!(observer.join().expect("shutdown observer exits"));
}
}
assert_eq!(
drop_status.load(Ordering::SeqCst),
1,
"{retirement:?}: the real worker's sink must re-enter without the slot lock"
);
assert!(matches!(*driver.lock_slot(), WorkerSlot::Closed));
}
#[test]
fn shutdown_retires_offered_worker_outside_handoff_lock() {
assert_worker_drop_can_reenter(WorkerRetirement::Offered);
}
#[test]
fn shutdown_retires_returned_worker_outside_handoff_lock() {
assert_worker_drop_can_reenter(WorkerRetirement::Returned);
}
#[test]
fn closed_loan_retires_worker_outside_handoff_lock() {
assert_worker_drop_can_reenter(WorkerRetirement::ClosedLoanReturn);
}
#[test]
fn background_shutdown_retires_offered_worker_outside_handoff_lock() {
assert_worker_drop_can_reenter(WorkerRetirement::BackgroundWaiting);
}
#[test]
fn background_handoff_refuses_queued_local_admission() {
use crate::runtime::RuntimeState;
use crate::runtime::spawn_mailbox::LocalSpawnRequest;
use crate::sync::ContendedMutex;
use crate::types::{Budget, Outcome};
let mut state = RuntimeState::new();
let root = state.create_root_region(Budget::INFINITE);
let pending = state.region(root).expect("root").pending_spawn_handle();
let state = Arc::new(ContendedMutex::new("queued_local_handoff", state));
let mailbox = Arc::new(SpawnMailbox::new());
let mut scheduler = ThreeLaneScheduler::new(1, &state);
scheduler.attach_spawn_mailbox(Arc::clone(&mailbox));
let worker = scheduler.take_workers().pop().expect("one real worker");
let shutdown = Arc::clone(&worker.shutdown);
let driver = Arc::new(CurrentThreadDriver::new(None, worker.local_store_key()));
*driver.lock_slot() = WorkerSlot::Requested(std::thread::current().id());
driver.handover_requested.store(true, Ordering::Release);
let observer_driver = Arc::clone(&driver);
let observer_shutdown = Arc::clone(&shutdown);
let observer = std::thread::spawn(move || {
let (slot, timeout) = observer_driver
.changed
.wait_timeout_while(
observer_driver.lock_slot(),
Duration::from_secs(5),
|slot| matches!(slot, WorkerSlot::Requested(_)),
)
.unwrap_or_else(PoisonError::into_inner);
let offered = matches!(*slot, WorkerSlot::Offered(_, _));
let stuck = timeout.timed_out() && matches!(*slot, WorkerSlot::Requested(_));
drop(slot);
if offered || stuck {
observer_shutdown.store(true, Ordering::Release);
observer_driver.shutdown();
}
(offered, stuck)
});
let ran_on = Rc::new(Cell::new(None));
let task_ran_on = Rc::clone(&ran_on);
driver.run_background(worker, || {
spawn_mailbox::enqueue_local_spawn_for_mailbox(
LocalSpawnRequest {
task_id: mailbox.allocate_task_id(),
region: root,
budget: Budget::INFINITE,
factory: Box::new(move |_| {
Box::pin(async move {
task_ran_on.set(Some(std::thread::current().id()));
shutdown.store(true, Ordering::Release);
Outcome::Ok(())
})
}),
on_unadmitted_cancel: None,
on_admission_error: None,
pending_reservation: Some(pending.reserve()),
admitted_slot: None,
},
&mailbox,
);
assert_eq!(pending.count(), 1, "request is queued before handoff");
});
let (offered, stuck) = observer.join().expect("handoff observer exits");
spawn_mailbox::cancel_local_spawns_for_mailbox(&mailbox);
assert!(!stuck, "background worker must answer the handoff request");
assert!(
!offered,
"queued local admission must prevent a worker loan"
);
assert_eq!(ran_on.get(), Some(std::thread::current().id()));
assert_eq!(pending.count(), 0, "pending admission credit is released");
assert_eq!(Rc::strong_count(&ran_on), 1, "local capture is retired");
}
#[test]
fn refused_request_does_not_wait_for_a_foreign_loan() {
assert_refused_request_returns(WorkerSlot::Loaned);
}
#[test]
fn refused_request_does_not_wait_for_a_new_requester() {
assert_refused_request_returns(WorkerSlot::Requested(std::thread::current().id()));
}
#[test]
fn refused_request_does_not_steal_a_new_requesters_offer() {
assert_refused_request_returns(WorkerSlot::Offered(
std::thread::current().id(),
Box::new(()),
));
}
fn assert_refused_request_returns(replacement: WorkerSlot) {
use crate::runtime::RuntimeState;
use crate::sync::ContendedMutex;
use std::sync::mpsc;
let state = Arc::new(ContendedMutex::new("request_race", RuntimeState::new()));
let scheduler = Arc::new(ThreeLaneScheduler::new(1, &state));
let driver = Arc::new(CurrentThreadDriver::new(
None,
crate::runtime::local::allocate_local_store_key(),
));
let caller_driver = Arc::clone(&driver);
let (sent, received) = mpsc::channel();
let caller = std::thread::spawn(move || {
let refused = caller_driver.acquire(&scheduler).is_none();
let _ = sent.send(refused);
});
let deadline = Instant::now() + Duration::from_secs(2);
let mut request_seen = false;
let expected_state = std::mem::discriminant(&replacement);
while Instant::now() < deadline {
let mut slot = driver.lock_slot();
if driver.handover_requested.load(Ordering::Acquire) {
request_seen = true;
*slot = replacement;
driver.changed.notify_all();
break;
}
drop(slot);
std::thread::yield_now();
}
let result = received.recv_timeout(Duration::from_secs(2));
let final_state = std::mem::discriminant(&*driver.lock_slot());
let flag_retained = driver.handover_requested.load(Ordering::Acquire);
driver.shutdown();
caller.join().expect("requester exits after cleanup");
assert!(
request_seen,
"requester must actually enter the handover wait"
);
assert!(result.expect("refused requester waited on a foreign loan"));
assert_eq!(final_state, expected_state, "foreign slot was consumed");
assert!(
flag_retained,
"refused caller cleared a foreign request flag"
);
}
#[test]
fn scoped_local_store_key_is_not_send() {
trait AmbiguousIfSend<A> {
fn check() {}
}
impl<T> AmbiguousIfSend<()> for T {}
impl<T: Send> AmbiguousIfSend<u8> for T {}
<ScopedLocalStoreKey as AmbiguousIfSend<_>>::check();
}
#[test]
fn current_thread_driver_is_send_and_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<CurrentThreadDriver>();
}
#[test]
fn shutdown_retires_the_calling_threads_keyed_store() {
use crate::runtime::local::{
ScopedLocalStoreKey, keyed_local_store_count, local_task_count,
};
let before = keyed_local_store_count();
let driver_key = crate::runtime::local::allocate_local_store_key();
let other_key = crate::runtime::local::allocate_local_store_key();
let driver = CurrentThreadDriver::new(None, driver_key);
let other = CurrentThreadDriver::new(None, other_key);
{
let _key = ScopedLocalStoreKey::new(driver_key);
assert_eq!(local_task_count(), 0);
}
{
let _key = ScopedLocalStoreKey::new(other_key);
assert_eq!(local_task_count(), 0);
}
assert_eq!(keyed_local_store_count(), before + 2);
driver.shutdown();
assert_eq!(keyed_local_store_count(), before + 1);
other.shutdown();
assert_eq!(keyed_local_store_count(), before);
}
}