use crate::error::Error;
pub(crate) trait Steps {
type Output;
fn step(&mut self) -> Result<Option<Self::Output>, Error>;
}
pub(crate) fn run<S: Steps>(mut job: S) -> Result<S::Output, Error> {
loop {
if let Some(output) = job.step()? {
return Ok(output);
}
}
}
#[cfg(feature = "tokio")]
pub(crate) async fn run_async<S: Steps>(mut job: S) -> Result<S::Output, Error> {
loop {
if let Some(output) = job.step()? {
return Ok(output);
}
tokio::task::consume_budget().await;
}
}
#[cfg(test)]
mod tests {
use super::{Steps, run};
use crate::error::{Error, ErrorKind};
pub(super) struct Count {
next: u32,
end: u32,
fail_at: u32,
seen: Vec<u32>,
}
impl Steps for Count {
type Output = Vec<u32>;
fn step(&mut self) -> Result<Option<Vec<u32>>, Error> {
if self.next == self.fail_at {
return Err(Error::body("the failing step"));
}
if self.next == self.end {
return Ok(Some(std::mem::take(&mut self.seen)));
}
self.seen.push(self.next);
self.next += 1;
Ok(None)
}
}
pub(super) fn count(end: u32) -> Count {
Count {
next: 0,
end,
fail_at: u32::MAX,
seen: Vec::new(),
}
}
#[test]
fn a_job_runs_every_step_in_order() {
assert_eq!(run(count(3)).expect("run"), [0, 1, 2]);
assert!(run(count(0)).expect("run").is_empty());
}
#[test]
fn a_failed_step_ends_the_run_with_its_error() {
let err = run(Count {
fail_at: 99,
..count(200)
})
.expect_err("step 99 fails");
assert_eq!(err.kind(), ErrorKind::Body);
}
#[cfg(feature = "tokio")]
mod with_tokio {
use std::future::Future;
use std::pin::pin;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::task::{Context, Poll, Waker};
use super::super::{Steps, run, run_async};
use super::count;
use crate::error::Error;
#[test]
fn the_async_driver_gives_the_sync_result_and_does_not_yield_outside_a_runtime() {
let expected = run(count(10_000)).expect("run");
let mut future = pin!(run_async(count(10_000)));
let Poll::Ready(result) = future
.as_mut()
.poll(&mut Context::from_waker(Waker::noop()))
else {
panic!("no runtime, no budget, so no yield");
};
assert_eq!(result.expect("run"), expected);
}
struct Watch {
flag: Arc<AtomicBool>,
left: u32,
saw_flag: bool,
}
impl Steps for Watch {
type Output = bool;
fn step(&mut self) -> Result<Option<bool>, Error> {
self.saw_flag |= self.flag.load(Ordering::SeqCst);
if self.left == 0 {
return Ok(Some(self.saw_flag));
}
self.left -= 1;
Ok(None)
}
}
#[test]
fn a_long_job_lets_another_task_run_on_a_single_thread() {
let runtime = tokio::runtime::Builder::new_current_thread()
.build()
.expect("runtime");
let saw_flag = runtime.block_on(async {
let flag = Arc::new(AtomicBool::new(false));
let setter = Arc::clone(&flag);
let other = tokio::spawn(async move { setter.store(true, Ordering::SeqCst) });
let saw_flag = run_async(Watch {
flag,
left: 10_000,
saw_flag: false,
})
.await
.expect("run");
other.await.expect("the other task");
saw_flag
});
assert!(
saw_flag,
"the job never gave the thread to the other task before it ended"
);
}
}
}