#[cfg(any(unix, windows))]
use std::path::Path;
#[cfg(unix)]
use std::process::{Command, ExitStatus, Stdio};
use std::{
any::Any,
fmt,
future::Future,
io,
marker::PhantomData,
net::SocketAddr,
pin::Pin,
sync::Arc,
task::{Context, Poll},
time::Duration,
};
use super::{Interest, IoSource, traits};
pub(crate) type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
pub(crate) trait ErasedRuntime: Send + Sync {
fn register_io_source(&self, source: IoSource) -> io::Result<Box<dyn ErasedRegistration>>;
fn sleep(&self, duration: Duration) -> BoxFuture<'static, ()>;
fn spawn(
&self,
name: &str,
future: BoxFuture<'static, Box<dyn Any + Send>>,
) -> Box<dyn ErasedTask>;
fn connect_tcp(&self, address: SocketAddr) -> BoxFuture<'_, io::Result<IoSource>>;
#[cfg(any(unix, windows))]
fn connect_unix<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, io::Result<IoSource>>;
#[cfg(unix)]
fn spawn_process(
&self,
command: Command,
stdin: Stdio,
stdout: Stdio,
stderr: Stdio,
) -> io::Result<BoxFuture<'static, io::Result<ExitStatus>>>;
fn spawn_blocking(
&self,
work: Box<dyn FnOnce() -> Box<dyn Any + Send> + Send>,
) -> BoxFuture<'static, Box<dyn Any + Send>>;
}
impl fmt::Debug for dyn ErasedRuntime {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("<external runtime>")
}
}
impl<R> ErasedRuntime for R
where
R: traits::Runtime,
{
fn register_io_source(&self, source: IoSource) -> io::Result<Box<dyn ErasedRegistration>> {
traits::Runtime::register_io_source(self, source)
.map(|registration| Box::new(registration) as Box<dyn ErasedRegistration>)
}
fn sleep(&self, duration: Duration) -> BoxFuture<'static, ()> {
Box::pin(traits::Runtime::sleep(self, duration))
}
fn spawn(
&self,
name: &str,
future: BoxFuture<'static, Box<dyn Any + Send>>,
) -> Box<dyn ErasedTask> {
Box::new(traits::Runtime::spawn(self, name, future))
}
fn connect_tcp(&self, address: SocketAddr) -> BoxFuture<'_, io::Result<IoSource>> {
Box::pin(traits::Runtime::connect_tcp(self, address))
}
#[cfg(any(unix, windows))]
fn connect_unix<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, io::Result<IoSource>> {
Box::pin(traits::Runtime::connect_unix(self, path))
}
#[cfg(unix)]
fn spawn_process(
&self,
command: Command,
stdin: Stdio,
stdout: Stdio,
stderr: Stdio,
) -> io::Result<BoxFuture<'static, io::Result<ExitStatus>>> {
traits::Runtime::spawn_process(self, command, stdin, stdout, stderr)
}
fn spawn_blocking(
&self,
work: Box<dyn FnOnce() -> Box<dyn Any + Send> + Send>,
) -> BoxFuture<'static, Box<dyn Any + Send>> {
traits::Runtime::spawn_blocking(self, work)
}
}
impl traits::Runtime for Arc<dyn ErasedRuntime> {
type RegisteredIoSource = Box<dyn ErasedRegistration>;
type Sleep = BoxFuture<'static, ()>;
type Task<T>
= ExternalTask<T>
where
T: Send + 'static;
fn register_io_source(&self, source: IoSource) -> io::Result<Self::RegisteredIoSource> {
ErasedRuntime::register_io_source(&**self, source)
}
fn sleep(&self, duration: Duration) -> Self::Sleep {
ErasedRuntime::sleep(&**self, duration)
}
fn spawn<T>(
&self,
name: &str,
future: impl Future<Output = T> + Send + 'static,
) -> ExternalTask<T>
where
T: Send + 'static,
{
ExternalTask::spawn(&**self, name, future)
}
fn connect_tcp(
&self,
address: SocketAddr,
) -> impl Future<Output = io::Result<IoSource>> + Send {
ErasedRuntime::connect_tcp(&**self, address)
}
#[cfg(any(unix, windows))]
async fn connect_unix(&self, path: &Path) -> io::Result<IoSource> {
ErasedRuntime::connect_unix(&**self, path).await
}
#[cfg(unix)]
fn spawn_process(
&self,
command: Command,
stdin: Stdio,
stdout: Stdio,
stderr: Stdio,
) -> io::Result<BoxFuture<'static, io::Result<ExitStatus>>> {
ErasedRuntime::spawn_process(&**self, command, stdin, stdout, stderr)
}
fn spawn_blocking<T>(&self, work: impl FnOnce() -> T + Send + 'static) -> BoxFuture<'static, T>
where
T: Send + 'static,
{
let work = Box::new(move || Box::new(work()) as Box<dyn Any + Send>);
let outcome = ErasedRuntime::spawn_blocking(&**self, work);
Box::pin(async move {
*outcome
.await
.downcast()
.expect("blocking work hands back the value it produced")
})
}
}
pub(crate) trait ErasedRegistration: Send + Sync {
fn poll_io(
&self,
cx: &mut Context<'_>,
interest: Interest,
operation: &mut dyn FnMut() -> io::Result<()>,
) -> Poll<io::Result<()>>;
}
impl fmt::Debug for dyn ErasedRegistration {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("<external registration>")
}
}
impl<R> ErasedRegistration for R
where
R: traits::PollIo,
{
fn poll_io(
&self,
cx: &mut Context<'_>,
interest: Interest,
operation: &mut dyn FnMut() -> io::Result<()>,
) -> Poll<io::Result<()>> {
traits::PollIo::poll_io(self, cx, interest, operation)
}
}
impl traits::PollIo for Box<dyn ErasedRegistration> {
fn poll_io<T>(
&self,
cx: &mut Context<'_>,
interest: Interest,
mut operation: impl FnMut() -> io::Result<T>,
) -> Poll<io::Result<T>> {
let mut value = None;
let ready = ErasedRegistration::poll_io(&**self, cx, interest, &mut || {
value = Some(operation()?);
Ok(())
});
match ready {
Poll::Ready(result) => Poll::Ready(result.map(|()| {
value
.take()
.expect("a successful operation produced a value")
})),
Poll::Pending => {
assert!(
value.is_none(),
"a registration must not report `Pending` for an operation that produced a \
value",
);
Poll::Pending
}
}
}
}
pub(crate) trait ErasedTask:
Future<Output = io::Result<Box<dyn Any + Send>>> + Send + Sync + Unpin
{
fn detach(self: Box<Self>);
}
impl fmt::Debug for dyn ErasedTask {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("<external task>")
}
}
impl<H> ErasedTask for H
where
H: traits::TaskHandle<Box<dyn Any + Send>>,
{
fn detach(self: Box<Self>) {
traits::TaskHandle::detach(*self)
}
}
pub(crate) struct ExternalTask<T> {
inner: Box<dyn ErasedTask>,
value: PhantomData<fn() -> T>,
}
impl<T> ExternalTask<T>
where
T: Send + 'static,
{
pub(crate) fn spawn(
runtime: &dyn ErasedRuntime,
name: &str,
future: impl Future<Output = T> + Send + 'static,
) -> Self {
let future = Box::pin(async move { Box::new(future.await) as Box<dyn Any + Send> });
Self {
inner: runtime.spawn(name, future),
value: PhantomData,
}
}
}
impl<T> fmt::Debug for ExternalTask<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.inner, f)
}
}
impl<T> Future for ExternalTask<T>
where
T: 'static,
{
type Output = io::Result<T>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut self.get_mut().inner)
.poll(cx)
.map_ok(|value| *value.downcast().expect(PRODUCED))
}
}
impl<T> traits::TaskHandle<T> for ExternalTask<T>
where
T: Send + 'static,
{
fn detach(self) {
ErasedTask::detach(self.inner)
}
}
const PRODUCED: &str = "a task hands back the value its future produced";