use std::any::type_name;
use std::fmt::Debug;
use std::thread::ThreadId;
use arty_executor::remote::Pool as RemotePool;
use performables::arc::Arc;
use crate::runtime::Error;
use crate::runtime::blocking_worker::BlockingWorker;
use crate::runtime::dispatch::{DispatcherCore, WorkerIndex};
use crate::task::Builtins;
use crate::task::join::JoinHandle;
#[derive(Clone)]
pub(crate) struct DispatcherClient {
core: Arc<DispatcherCore>,
}
impl DispatcherClient {
pub(crate) const fn new(core: Arc<DispatcherCore>) -> Self {
Self { core }
}
pub(crate) fn worker_index(&self, thread_id: ThreadId) -> Option<WorkerIndex> {
self.core.worker_index(thread_id)
}
pub(crate) fn blocking_worker(&self, worker_index: WorkerIndex) -> Arc<BlockingWorker> {
self.core.blocking_worker(worker_index)
}
pub(crate) fn wake_worker(&self, worker_index: WorkerIndex) {
self.core.wake_worker(worker_index);
}
pub(crate) fn next_blocking_worker(&self) -> Arc<BlockingWorker> {
self.core.next_blocking_worker()
}
pub(crate) fn owns(&self, thread: &thread_aware::Thread) -> bool {
self.core.owns(thread)
}
pub(crate) fn is_shutting_down(&self) -> bool {
self.core.is_shutting_down()
}
pub(crate) fn shutdown_signal(&self) -> Arc<std::sync::atomic::AtomicBool> {
self.core.shutdown_signal()
}
pub(crate) fn remote_results(&self) -> &RemotePool {
self.core.remote_results()
}
#[cfg_attr(test, mutants::skip)] pub(crate) fn is_current_blocking_thread(&self) -> bool {
self.core.is_current_blocking_thread()
}
pub(crate) fn spawn_on_worker<FF, F, R>(&self, worker_index: WorkerIndex, future_factory: FF) -> JoinHandle<R>
where
FF: FnOnce(Builtins) -> F + Send + 'static,
F: Future<Output = R> + 'static,
R: Send + 'static,
{
self.core.spawn_on_worker(worker_index, future_factory)
}
pub(crate) fn spawn<FF, F, R>(&self, future_factory: FF) -> JoinHandle<R>
where
FF: FnOnce(Builtins) -> F + Send + 'static,
F: Future<Output = R> + 'static,
R: Send + 'static,
{
self.core.spawn(future_factory)
}
pub(crate) fn spawn_everywhere<M, FF, F, R>(&self, make_factory: M) -> Vec<JoinHandle<R>>
where
M: FnMut() -> FF,
FF: FnOnce(Builtins) -> F + Send + 'static,
F: Future<Output = R> + 'static,
R: Send + 'static,
{
self.core.spawn_everywhere(make_factory)
}
#[cfg_attr(test, mutants::skip)] pub(crate) fn stop(&self) {
self.core.stop();
}
#[cfg_attr(test, mutants::skip)]
pub(crate) fn wait(&self) -> Result<(), Error> {
self.core.join()
}
}
impl Debug for DispatcherClient {
#[cfg_attr(coverage_nightly, coverage(off))] #[cfg_attr(test, mutants::skip)] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct(type_name::<Self>()).finish()
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))] mod tests {
use std::task::Waker;
use observed::Sink;
use performables::sync::channel;
use thread_aware::ThreadAware;
use super::*;
use crate::runtime::blocking_worker::BlockingPool;
use crate::runtime::dispatch::{WorkerEndpoint, test_threads};
use crate::runtime::thread::waiter::ThreadWaiter;
use crate::task::scheduler::Scheduler;
#[test]
fn clients_observe_the_shared_shutdown_state() {
let (command_tx, _commands) = channel::unbounded();
let worker = WorkerEndpoint {
command_tx,
waker: Waker::noop().clone(),
thread: test_threads(1).pop().unwrap(),
blocking_worker: BlockingWorker::new(BlockingPool::new(None), Sink::noop()),
};
let client = DispatcherClient::new(Arc::new(DispatcherCore::new(
ThreadWaiter::new(vec![]),
nonempty::NonEmpty::new(worker),
Sink::noop(),
)));
let clone = client.clone();
assert!(std::ptr::eq(client.remote_results(), client.core.remote_results()));
assert!(!client.is_shutting_down());
assert!(!clone.is_shutting_down());
client.stop();
assert!(client.is_shutting_down());
assert!(clone.is_shutting_down());
}
#[test]
fn relocate_updates_task_placement() {
let (worker0_tx, worker0_rx) = channel::unbounded();
let (worker1_tx, worker1_rx) = channel::unbounded();
let wfs = ThreadWaiter::new(vec![]);
let threads = test_threads(2);
let blocking_worker = BlockingWorker::new(BlockingPool::new(None), Sink::noop());
let dispatcher = Arc::new(DispatcherCore::new(
wfs,
nonempty::NonEmpty::from_vec(vec![
WorkerEndpoint {
command_tx: worker0_tx,
waker: Waker::noop().clone(),
thread: threads[0].clone(),
blocking_worker: Arc::clone(&blocking_worker),
},
WorkerEndpoint {
command_tx: worker1_tx,
waker: Waker::noop().clone(),
thread: threads[1].clone(),
blocking_worker: Arc::clone(&blocking_worker),
},
])
.unwrap(),
Sink::noop(),
));
let mut scheduler = Scheduler::new(DispatcherClient::new(dispatcher), threads[0].clone());
scheduler.relocate(Some(&threads[0]), &threads[1]);
scheduler.spawn(async |_| {});
assert!(
worker0_rx.try_recv().is_err(),
"task should not be dispatched to original worker after relocate"
);
assert!(worker1_rx.try_recv().is_ok(), "task should be dispatched to the relocated worker");
}
#[test]
fn relocate_switches_blocking_worker() {
let threads = test_threads(2);
let source_worker = BlockingWorker::new(BlockingPool::new(None), Sink::noop());
let destination_worker = BlockingWorker::new(BlockingPool::new(None), Sink::noop());
destination_worker.shutdown();
let dispatcher = Arc::new(DispatcherCore::new(
ThreadWaiter::new(vec![]),
nonempty::NonEmpty::from_vec(vec![
WorkerEndpoint {
command_tx: channel::unbounded().0,
waker: Waker::noop().clone(),
thread: threads[0].clone(),
blocking_worker: Arc::clone(&source_worker),
},
WorkerEndpoint {
command_tx: channel::unbounded().0,
waker: Waker::noop().clone(),
thread: threads[1].clone(),
blocking_worker: Arc::clone(&destination_worker),
},
])
.unwrap(),
Sink::noop(),
));
let mut scheduler = Scheduler::new(DispatcherClient::new(dispatcher), threads[0].clone());
let before = (
Arc::ptr_eq(scheduler.blocking_worker(), &source_worker),
Arc::ptr_eq(scheduler.blocking_worker(), &destination_worker),
);
scheduler.relocate(Some(&threads[0]), &threads[1]);
let after = (
Arc::ptr_eq(scheduler.blocking_worker(), &source_worker),
Arc::ptr_eq(scheduler.blocking_worker(), &destination_worker),
);
assert_eq!((before, after), ((true, false), (false, true)));
}
}