use crate::runtime::builder::RuntimeBuilder;
use crate::runtime::dispatch::DispatcherClient;
use crate::runtime::error::Error;
use crate::task::{RuntimeScheduler, Scheduler};
#[derive(Debug)]
pub struct Runtime {
scheduler: RuntimeScheduler,
shutdown_on_drop: bool,
}
impl Runtime {
pub fn new() -> Result<Self, Error> {
RuntimeBuilder::new().build()
}
#[must_use]
pub fn builder() -> RuntimeBuilder {
RuntimeBuilder::new()
}
#[must_use]
#[inline]
pub fn scheduler(&self) -> &RuntimeScheduler {
&self.scheduler
}
pub fn stop(mut self) -> Result<(), Error> {
self.scheduler.dispatcher.stop();
self.shutdown_on_drop = false;
self.wait()
}
fn wait(&self) -> Result<(), Error> {
if Scheduler::is_current_worker_thread() {
return Err(Error::shutdown_wait_from_worker());
}
if self.scheduler.dispatcher.is_current_blocking_task() {
return Err(Error::shutdown_wait_from_blocking_callback());
}
self.scheduler.dispatcher.wait()
}
pub(in crate::runtime) const fn with_dispatcher(dispatcher: DispatcherClient) -> Self {
Self {
scheduler: RuntimeScheduler::new(dispatcher),
shutdown_on_drop: true,
}
}
}
impl Drop for Runtime {
fn drop(&mut self) {
if !self.shutdown_on_drop {
return;
}
self.scheduler.dispatcher.stop();
let _ = self.wait();
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use std::task::Waker;
use std::thread;
use observed::Sink;
use performables::arc::Arc;
use performables::sync::channel;
use testing_aids::TEST_TIMEOUT;
use thread_aware::{ThreadBuilder, Unaware};
use super::*;
use crate::runtime::blocking_worker::{BlockingPool, BlockingWorker};
use crate::runtime::dispatch::{DispatcherCore, WorkerEndpoint};
use crate::runtime::thread::waiter::ThreadWaiter;
#[test]
fn explicit_stop_reports_a_worker_panic_after_joining() {
let worker = thread::spawn(|| panic!("worker shutdown failure"));
let endpoint = WorkerEndpoint {
command_tx: channel::unbounded().0,
waker: Waker::noop().clone(),
thread: ThreadBuilder::default().build(worker.thread().id()),
blocking_worker: BlockingWorker::new(BlockingPool::new(None), Sink::noop()),
};
let dispatcher = DispatcherClient::new(Arc::new(DispatcherCore::new(
ThreadWaiter::new(vec![worker]),
nonempty::NonEmpty::new(endpoint),
Sink::noop(),
)));
let runtime = Runtime::with_dispatcher(dispatcher);
assert!(runtime.stop().unwrap_err().to_string().contains("panicked"));
}
#[test]
fn blocking_callback_is_detected_before_waiting_for_shutdown() {
let runtime = Runtime::builder()
.workers(crate::runtime::WorkersPolicy::exactly(1))
.build()
.unwrap();
let dispatcher = runtime.scheduler.dispatcher.clone();
assert!(
runtime
.scheduler()
.spawn_blocking(move || dispatcher.is_current_blocking_task())
.join()
.unwrap()
);
runtime.stop().unwrap();
}
#[test]
fn dropping_the_owner_on_its_async_worker_requests_shutdown_without_unwinding() {
let runtime = Runtime::builder()
.workers(crate::runtime::WorkersPolicy::exactly(1))
.build()
.unwrap();
let dispatcher = runtime.scheduler.dispatcher.clone();
let scheduler = runtime
.scheduler()
.spawn_anywhere((), |cx, ()| async move { cx.scheduler().clone() })
.join()
.unwrap();
assert!(scheduler.spawn(async |_| Scheduler::is_current_worker_thread()).join().unwrap());
scheduler.spawn(async move |_| drop(runtime)).join().unwrap();
assert!(dispatcher.is_shutting_down());
dispatcher.wait().unwrap();
assert!(scheduler.spawn(async |_| 42).join().unwrap_err().is_shutdown());
}
#[test]
fn dropping_the_owner_on_an_async_worker_does_not_wait_for_shutdown() {
let runtime = Runtime::builder()
.workers(crate::runtime::WorkersPolicy::exactly(1))
.build()
.unwrap();
let dispatcher = runtime.scheduler.dispatcher.clone();
let scheduler = runtime
.scheduler()
.spawn_anywhere((), |cx, ()| async move { cx.scheduler().clone() })
.join()
.unwrap();
scheduler.spawn(async move |_| drop(runtime)).join().unwrap();
assert!(dispatcher.is_shutting_down());
dispatcher.wait().unwrap();
}
#[test]
fn dropping_another_runtime_on_a_worker_does_not_wait_for_blocking_work() {
let runtime = Runtime::builder()
.workers(crate::runtime::WorkersPolicy::exactly(1))
.build()
.unwrap();
let caller = Runtime::builder()
.workers(crate::runtime::WorkersPolicy::exactly(1))
.build()
.unwrap();
let dispatcher = runtime.scheduler.dispatcher.clone();
let (started, ready) = channel::unbounded();
let (release, released) = channel::unbounded();
let blocking = runtime.scheduler().spawn_blocking(move || {
started.send(()).unwrap();
released.recv_timeout(TEST_TIMEOUT).unwrap();
42
});
ready.recv_timeout(TEST_TIMEOUT).unwrap();
assert!(
caller
.scheduler()
.spawn_anywhere((), |_, ()| async { Scheduler::is_current_worker_thread() })
.join()
.unwrap()
);
caller
.scheduler()
.spawn_anywhere(Unaware(runtime), |_, Unaware(runtime)| async move { drop(runtime) })
.join()
.unwrap();
assert!(dispatcher.is_shutting_down());
release.send(()).unwrap();
assert_eq!(blocking.join().unwrap(), 42);
dispatcher.wait().unwrap();
caller.stop().unwrap();
}
}