use std::fmt;
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use tokio::task;
use crate::{Error, ErrorKind, Result};
pub struct JoinHandle<T>(task::JoinHandle<T>);
impl<T> Unpin for JoinHandle<T> {}
impl<T: Send + 'static> Future for JoinHandle<T> {
type Output = crate::Result<T>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut self.get_mut().0).poll(cx).map(|r| {
r.map_err(|e| Error::new(ErrorKind::Unexpected, "spawned task failed").with_source(e))
})
}
}
#[derive(Clone)]
pub struct RuntimeHandle {
handle: tokio::runtime::Handle,
}
impl fmt::Debug for RuntimeHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RuntimeHandle").finish()
}
}
impl RuntimeHandle {
fn from_tokio_handle(handle: tokio::runtime::Handle) -> Self {
Self { handle }
}
pub fn spawn<F>(&self, future: F) -> JoinHandle<F::Output>
where
F: Future + Send + 'static,
F::Output: Send + 'static,
{
JoinHandle(self.handle.spawn(future))
}
pub fn spawn_blocking<F, T>(&self, f: F) -> JoinHandle<T>
where
F: FnOnce() -> T + Send + 'static,
T: Send + 'static,
{
JoinHandle(self.handle.spawn_blocking(f))
}
}
#[derive(Clone)]
pub struct Runtime {
io: RuntimeHandle,
cpu: RuntimeHandle,
}
impl fmt::Debug for Runtime {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Runtime").finish()
}
}
impl Runtime {
pub fn new(runtime: &tokio::runtime::Runtime) -> Self {
let handle = RuntimeHandle::from_tokio_handle(runtime.handle().clone());
Self {
io: handle.clone(),
cpu: handle,
}
}
pub fn new_with_split(
io_runtime: &tokio::runtime::Runtime,
cpu_runtime: &tokio::runtime::Runtime,
) -> Self {
Self {
io: RuntimeHandle::from_tokio_handle(io_runtime.handle().clone()),
cpu: RuntimeHandle::from_tokio_handle(cpu_runtime.handle().clone()),
}
}
pub fn current() -> Self {
Self::try_current().expect(
"Runtime::current() called outside a tokio runtime context. \
Call it from within #[tokio::main] / #[tokio::test], or construct \
a Runtime explicitly via Runtime::new / Runtime::new_with_split.",
)
}
pub fn try_current() -> Result<Self> {
let handle = tokio::runtime::Handle::try_current().map_err(|e| {
Error::new(
ErrorKind::Unexpected,
"no tokio runtime in context; call Runtime::try_current() \
from within a tokio runtime, or construct a Runtime explicitly \
via Runtime::new / Runtime::new_with_split",
)
.with_source(e)
})?;
let rh = RuntimeHandle::from_tokio_handle(handle);
Ok(Self {
io: rh.clone(),
cpu: rh,
})
}
pub fn io(&self) -> &RuntimeHandle {
&self.io
}
pub fn cpu(&self) -> &RuntimeHandle {
&self.cpu
}
}
#[cfg(test)]
mod tests {
use super::*;
struct TestRuntime {
tokio: tokio::runtime::Runtime,
rt: Runtime,
}
impl TestRuntime {
fn new() -> Self {
let tokio = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.expect("Failed to build tokio runtime");
let rt = Runtime::new(&tokio);
Self { tokio, rt }
}
fn block_on<F: Future>(&self, f: F) -> F::Output {
self.tokio.block_on(f)
}
}
#[test]
fn test_runtime_spawn_io() {
let h = TestRuntime::new();
let handle = h.rt.io().spawn(async { 1 + 1 });
assert_eq!(h.block_on(handle).unwrap(), 2);
}
#[test]
fn test_runtime_spawn_cpu() {
let h = TestRuntime::new();
let handle = h.rt.cpu().spawn(async { 3 + 4 });
assert_eq!(h.block_on(handle).unwrap(), 7);
}
#[test]
fn test_runtime_spawn_blocking() {
let h = TestRuntime::new();
let handle = h.rt.cpu().spawn_blocking(|| 1 + 1);
assert_eq!(h.block_on(handle).unwrap(), 2);
}
#[test]
fn test_runtime_new_with_custom_runtime() {
let h = TestRuntime::new();
let handle = h.rt.io().spawn(async { 42 });
assert_eq!(h.block_on(handle).unwrap(), 42);
}
#[test]
fn test_runtime_split_uses_separate_handles() {
let io_rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
let cpu_rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
let rt = Runtime::new_with_split(&io_rt, &cpu_rt);
let io_result = io_rt.block_on(async { rt.io().spawn(async { "io" }).await.unwrap() });
let cpu_result = cpu_rt.block_on(async { rt.cpu().spawn(async { "cpu" }).await.unwrap() });
assert_eq!(io_result, "io");
assert_eq!(cpu_result, "cpu");
}
#[test]
fn test_runtime_clone() {
let h = TestRuntime::new();
let rt2 = h.rt.clone();
let handle = rt2.io().spawn(async { 5 });
assert_eq!(h.block_on(handle).unwrap(), 5);
}
#[test]
fn test_runtime_debug() {
let h = TestRuntime::new();
let debug_str = format!("{:?}", h.rt);
assert!(debug_str.contains("Runtime"));
}
#[tokio::test(flavor = "multi_thread")]
async fn test_try_current_in_runtime() {
let rt = Runtime::try_current().expect("should find current runtime");
let result = rt.io().spawn(async { 7 }).await.unwrap();
assert_eq!(result, 7);
}
#[test]
fn test_try_current_outside_runtime() {
let err = Runtime::try_current().expect_err("must fail outside runtime");
assert_eq!(err.kind(), ErrorKind::Unexpected);
}
#[test]
fn test_spawn_after_runtime_drop_errors() {
let driver = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
let owned = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
let rt = Runtime::new(&owned);
drop(owned);
let handle = rt.io().spawn(async { 1 });
let result = driver.block_on(handle);
assert!(result.is_err(), "expected error after runtime shutdown");
}
}