#[cfg(unix)]
use std::process::{Command, ExitStatus, Stdio};
use std::{
future::Future,
io,
net::SocketAddr,
path::Path,
pin::Pin,
sync::{Arc, Mutex, PoisonError},
task::{Context, Poll},
time::Duration,
};
use async_executor::Executor;
use async_io::{Async, Timer};
use socket2::{Domain, SockAddr};
#[cfg(unix)]
use super::erased::BoxFuture;
use super::{Interest, IoSource, Runtime, io::connect_blocking, traits};
pub(crate) fn under_every_runtime<Body, Fut>(body: Body)
where
Body: Fn(Runtime) -> Fut,
Fut: Future<Output = ()>,
{
under_the_polled_runtimes(&body);
#[cfg(feature = "tokio")]
under_tokio(&body);
}
fn under_the_polled_runtimes<Body, Fut>(body: &Body)
where
Body: Fn(Runtime) -> Fut,
Fut: Future<Output = ()>,
{
#[cfg(feature = "default-rt")]
futures_lite::future::block_on(body(Runtime::ZRuntime(
super::ZRuntime::new().expect("a runtime of zbus's own"),
)));
futures_lite::future::block_on(body(Runtime::from_external(TestRuntime::new())));
futures_lite::future::block_on(body(Runtime::from_external(ReadinessFirst::new())));
}
#[cfg(feature = "tokio")]
fn under_tokio<Body, Fut>(body: &Body)
where
Body: Fn(Runtime) -> Fut,
Fut: Future<Output = ()>,
{
let tokio = tokio::runtime::Runtime::new().unwrap();
tokio.block_on(async {
let runtime = super::Tokio::current().expect("a Tokio runtime is current");
body(Runtime::Tokio(runtime)).await
});
}
#[derive(Clone, Debug)]
pub(crate) struct TestRuntime {
executor: Arc<Executor<'static>>,
blocking_calls: Arc<Mutex<usize>>,
}
impl TestRuntime {
pub(crate) fn new() -> Self {
let executor = Arc::new(Executor::new());
let runner = executor.clone();
std::thread::Builder::new()
.name("zbus test runtime".into())
.spawn(move || futures_lite::future::block_on(runner.run(std::future::pending::<()>())))
.expect("failed to spawn the test runtime thread");
Self {
executor,
blocking_calls: Arc::default(),
}
}
pub(crate) fn blocking_calls(&self) -> usize {
*self
.blocking_calls
.lock()
.unwrap_or_else(PoisonError::into_inner)
}
pub(crate) fn is_empty(&self) -> bool {
self.executor.is_empty()
}
}
impl traits::Runtime for TestRuntime {
type RegisteredIoSource = Registration;
type Sleep = Sleep;
type Task<T>
= Task<T>
where
T: Send + 'static;
fn register_io_source(&self, source: IoSource) -> io::Result<Registration> {
Async::new(source).map(Registration)
}
fn sleep(&self, duration: Duration) -> Sleep {
Sleep(Timer::after(duration))
}
fn spawn<T>(&self, _name: &str, future: impl Future<Output = T> + Send + 'static) -> Task<T>
where
T: Send + 'static,
{
Task(self.executor.spawn(future))
}
fn connect_tcp(
&self,
address: SocketAddr,
) -> impl Future<Output = io::Result<IoSource>> + Send {
traits::Runtime::spawn_blocking(self, move || {
connect_blocking(Domain::for_address(address), &address.into())
})
}
async fn connect_unix(&self, path: &Path) -> io::Result<IoSource> {
let address = SockAddr::unix(path)?;
traits::Runtime::spawn_blocking(self, move || connect_blocking(Domain::UNIX, &address))
.await
}
#[cfg(unix)]
fn spawn_process(
&self,
mut command: Command,
stdin: Stdio,
stdout: Stdio,
stderr: Stdio,
) -> io::Result<BoxFuture<'static, io::Result<ExitStatus>>> {
let child = command.stdin(stdin).stdout(stdout).stderr(stderr).spawn();
drop(command);
let mut child = child?;
Ok(Box::pin(async move {
if let Some(status) = child.try_wait()? {
return Ok(status);
}
zruntime::unblock(move || child.wait()).await
}))
}
fn spawn_blocking<T>(
&self,
work: impl FnOnce() -> T + Send + 'static,
) -> Pin<Box<dyn Future<Output = T> + Send + 'static>>
where
T: Send + 'static,
{
*self
.blocking_calls
.lock()
.unwrap_or_else(PoisonError::into_inner) += 1;
Box::pin(zruntime::unblock(work))
}
}
#[derive(Clone, Debug)]
pub(crate) struct ReadinessFirst(TestRuntime);
impl ReadinessFirst {
pub(crate) fn new() -> Self {
Self(TestRuntime::new())
}
}
impl traits::Runtime for ReadinessFirst {
type RegisteredIoSource = ReadinessFirstRegistration;
type Sleep = Sleep;
type Task<T>
= Task<T>
where
T: Send + 'static;
fn register_io_source(&self, source: IoSource) -> io::Result<ReadinessFirstRegistration> {
Async::new(source).map(ReadinessFirstRegistration)
}
fn sleep(&self, duration: Duration) -> Sleep {
traits::Runtime::sleep(&self.0, duration)
}
fn spawn<T>(&self, name: &str, future: impl Future<Output = T> + Send + 'static) -> Task<T>
where
T: Send + 'static,
{
traits::Runtime::spawn(&self.0, name, future)
}
fn connect_tcp(
&self,
address: SocketAddr,
) -> impl Future<Output = io::Result<IoSource>> + Send {
traits::Runtime::connect_tcp(&self.0, address)
}
fn connect_unix(&self, path: &Path) -> impl Future<Output = io::Result<IoSource>> + Send {
traits::Runtime::connect_unix(&self.0, 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.0, command, stdin, stdout, stderr)
}
fn spawn_blocking<T>(
&self,
work: impl FnOnce() -> T + Send + 'static,
) -> Pin<Box<dyn Future<Output = T> + Send + 'static>>
where
T: Send + 'static,
{
traits::Runtime::spawn_blocking(&self.0, work)
}
}
#[derive(Clone, Debug)]
pub(crate) struct DefaultBlocking(TestRuntime);
impl DefaultBlocking {
pub(crate) fn new() -> Self {
Self(TestRuntime::new())
}
}
impl traits::Runtime for DefaultBlocking {
type RegisteredIoSource = Registration;
type Sleep = Sleep;
type Task<T>
= Task<T>
where
T: Send + 'static;
fn register_io_source(&self, source: IoSource) -> io::Result<Registration> {
traits::Runtime::register_io_source(&self.0, source)
}
fn sleep(&self, duration: Duration) -> Sleep {
traits::Runtime::sleep(&self.0, duration)
}
fn spawn<T>(&self, name: &str, future: impl Future<Output = T> + Send + 'static) -> Task<T>
where
T: Send + 'static,
{
traits::Runtime::spawn(&self.0, name, future)
}
fn connect_tcp(
&self,
address: SocketAddr,
) -> impl Future<Output = io::Result<IoSource>> + Send {
traits::Runtime::connect_tcp(&self.0, address)
}
fn connect_unix(&self, path: &Path) -> impl Future<Output = io::Result<IoSource>> + Send {
traits::Runtime::connect_unix(&self.0, 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.0, command, stdin, stdout, stderr)
}
}
#[derive(Clone, Debug)]
pub(crate) struct OwnConnects(TestRuntime);
impl OwnConnects {
pub(crate) fn new() -> Self {
Self(TestRuntime::new())
}
}
impl traits::Runtime for OwnConnects {
type RegisteredIoSource = Registration;
type Sleep = Sleep;
type Task<T>
= Task<T>
where
T: Send + 'static;
fn register_io_source(&self, source: IoSource) -> io::Result<Registration> {
traits::Runtime::register_io_source(&self.0, source)
}
fn sleep(&self, duration: Duration) -> Sleep {
traits::Runtime::sleep(&self.0, duration)
}
fn spawn<T>(&self, name: &str, future: impl Future<Output = T> + Send + 'static) -> Task<T>
where
T: Send + 'static,
{
traits::Runtime::spawn(&self.0, name, future)
}
fn connect_tcp(
&self,
address: SocketAddr,
) -> impl Future<Output = io::Result<IoSource>> + Send {
std::future::ready(Err(io::Error::other(format!(
"a connect of its own to {address}"
))))
}
fn connect_unix(&self, path: &Path) -> impl Future<Output = io::Result<IoSource>> + Send {
std::future::ready(Err(io::Error::other(format!(
"a connect of its own to {}",
path.display(),
))))
}
#[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.0, command, stdin, stdout, stderr)
}
}
#[cfg(all(unix, any(feature = "unixexec", feature = "ibus", target_os = "macos")))]
#[derive(Clone, Debug)]
pub(crate) struct OwnSpawn(TestRuntime);
#[cfg(all(unix, any(feature = "unixexec", feature = "ibus", target_os = "macos")))]
impl OwnSpawn {
pub(crate) fn new() -> Self {
Self(TestRuntime::new())
}
}
#[cfg(all(unix, any(feature = "unixexec", feature = "ibus", target_os = "macos")))]
impl traits::Runtime for OwnSpawn {
type RegisteredIoSource = Registration;
type Sleep = Sleep;
type Task<T>
= Task<T>
where
T: Send + 'static;
fn register_io_source(&self, source: IoSource) -> io::Result<Registration> {
traits::Runtime::register_io_source(&self.0, source)
}
fn sleep(&self, duration: Duration) -> Sleep {
traits::Runtime::sleep(&self.0, duration)
}
fn spawn<T>(&self, name: &str, future: impl Future<Output = T> + Send + 'static) -> Task<T>
where
T: Send + 'static,
{
traits::Runtime::spawn(&self.0, name, future)
}
fn connect_tcp(
&self,
address: SocketAddr,
) -> impl Future<Output = io::Result<IoSource>> + Send {
traits::Runtime::connect_tcp(&self.0, address)
}
fn connect_unix(&self, path: &Path) -> impl Future<Output = io::Result<IoSource>> + Send {
traits::Runtime::connect_unix(&self.0, path)
}
fn spawn_process(
&self,
command: Command,
_stdin: Stdio,
_stdout: Stdio,
_stderr: Stdio,
) -> io::Result<BoxFuture<'static, io::Result<ExitStatus>>> {
Err(io::Error::other(format!(
"a spawn of its own for {}",
command.get_program().to_string_lossy(),
)))
}
}
#[cfg(feature = "p2p")]
#[derive(Clone, Debug)]
pub(crate) struct AbortingRuntime {
inner: TestRuntime,
handles: Arc<Mutex<Vec<futures_util::future::AbortHandle>>>,
}
#[cfg(feature = "p2p")]
impl AbortingRuntime {
pub(crate) fn new() -> Self {
Self {
inner: TestRuntime::new(),
handles: Arc::default(),
}
}
pub(crate) fn abort_tasks(&self) {
let handles =
std::mem::take(&mut *self.handles.lock().unwrap_or_else(PoisonError::into_inner));
for handle in handles {
handle.abort();
}
}
}
#[cfg(feature = "p2p")]
impl traits::Runtime for AbortingRuntime {
type RegisteredIoSource = Registration;
type Sleep = Sleep;
type Task<T>
= AbortableTask<T>
where
T: Send + 'static;
fn register_io_source(&self, source: IoSource) -> io::Result<Registration> {
traits::Runtime::register_io_source(&self.inner, source)
}
fn sleep(&self, duration: Duration) -> Sleep {
traits::Runtime::sleep(&self.inner, duration)
}
fn spawn<T>(
&self,
name: &str,
future: impl Future<Output = T> + Send + 'static,
) -> AbortableTask<T>
where
T: Send + 'static,
{
let (future, handle) = futures_util::future::abortable(future);
self.handles
.lock()
.unwrap_or_else(PoisonError::into_inner)
.push(handle);
AbortableTask(traits::Runtime::spawn(&self.inner, name, future))
}
fn connect_tcp(
&self,
address: SocketAddr,
) -> impl Future<Output = io::Result<IoSource>> + Send {
traits::Runtime::connect_tcp(&self.inner, address)
}
fn connect_unix(&self, path: &Path) -> impl Future<Output = io::Result<IoSource>> + Send {
traits::Runtime::connect_unix(&self.inner, 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.inner, command, stdin, stdout, stderr)
}
}
#[derive(Debug)]
pub(crate) struct Registration(Async<IoSource>);
impl traits::PollIo for Registration {
fn poll_io<T>(
&self,
cx: &mut Context<'_>,
interest: Interest,
mut operation: impl FnMut() -> io::Result<T>,
) -> Poll<io::Result<T>> {
loop {
match operation() {
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {}
result => return Poll::Ready(result),
}
let ready = match interest {
Interest::Readable => self.0.poll_readable(cx),
Interest::Writable => self.0.poll_writable(cx),
};
if let Err(e) = std::task::ready!(ready) {
return Poll::Ready(Err(e));
}
}
}
}
#[derive(Debug)]
pub(crate) struct ReadinessFirstRegistration(Async<IoSource>);
impl traits::PollIo for ReadinessFirstRegistration {
fn poll_io<T>(
&self,
cx: &mut Context<'_>,
interest: Interest,
mut operation: impl FnMut() -> io::Result<T>,
) -> Poll<io::Result<T>> {
loop {
let ready = match interest {
Interest::Readable => self.0.poll_readable(cx),
Interest::Writable => self.0.poll_writable(cx),
};
if let Err(e) = std::task::ready!(ready) {
return Poll::Ready(Err(e));
}
match operation() {
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {}
result => return Poll::Ready(result),
}
}
}
}
#[derive(Debug)]
pub(crate) struct Sleep(Timer);
impl Future for Sleep {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
Pin::new(&mut self.0).poll(cx).map(drop)
}
}
#[derive(Debug)]
pub(crate) struct Task<T>(async_task::Task<T>);
impl<T> traits::TaskHandle<T> for Task<T>
where
T: Send + 'static,
{
fn detach(self) {
self.0.detach();
}
}
impl<T> Future for Task<T> {
type Output = io::Result<T>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut self.0).poll(cx).map(Ok)
}
}
#[cfg(feature = "p2p")]
pub(crate) struct AbortableTask<T>(Task<Result<T, futures_util::future::Aborted>>);
#[cfg(feature = "p2p")]
impl<T> Future for AbortableTask<T> {
type Output = io::Result<T>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut self.0).poll(cx).map(|outcome| match outcome {
Ok(Ok(value)) => Ok(value),
Ok(Err(_)) => Err(io::Error::other("the runtime aborted the task")),
Err(e) => Err(e),
})
}
}
#[cfg(feature = "p2p")]
impl<T> traits::TaskHandle<T> for AbortableTask<T>
where
T: Send + 'static,
{
fn detach(self) {
traits::TaskHandle::detach(self.0)
}
}