use std::sync::Arc;
use std::time::Duration;
use crate::checkpoint::{PendingStep, StepPlacement, StepWriter};
use crate::error::{DurableError, Error};
use crate::sysdb::types::{StepCaller, duration_to_ms, step_names};
pub fn sleep<E>(duration: Duration) -> PendingStep<'static, (), E>
where
E: DurableError + 'static,
{
let built = match duration_to_ms("duration", duration) {
Err(error) => Err(Error::refused_argument("sleep", error)),
Ok(_) => {
let placement = StepPlacement::here();
Ok((placement.executor().map(Arc::clone), placement))
}
};
PendingStep::placed(
step_names::SLEEP,
built,
move |executor, placement| async move {
let Some((
executor,
StepWriter {
workflow_id,
step_id,
owner_xid,
},
)) = executor.zip(placement.step_writer())
else {
tracing::debug!(
duration_ms = duration.as_millis(),
"the sleep is not checkpointed: it is outside a workflow, or inside a step"
);
crate::rt::sleep(duration).await;
return Ok(());
};
let wake = executor
.sysdb()
.record_sleep(
StepCaller {
workflow_id,
step_id,
codec: executor.serializer(),
owner_xid,
},
duration,
)
.await
.map_err(|error| Error::refused_argument("sleep", error))?;
let until = wake.arrives();
tracing::debug!(
step_id,
remaining_ms = until
.saturating_duration_since(crate::rt::now())
.as_millis(),
"sleeping until the recorded wake time"
);
crate::rt::sleep_until(until).await;
Ok(())
},
)
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn a_sleep_that_cannot_be_recorded_is_refused_outside_a_workflow() {
let refused = sleep::<crate::error::EngineOnly>(Duration::MAX).await;
match refused {
Err(Error::InvalidArgument { operation, detail }) => {
assert_eq!(operation, "sleep");
assert_eq!(
detail,
"`duration` must fit in milliseconds as a 64-bit integer"
);
}
other => panic!("expected the sleep to be refused, got {other:?}"),
}
}
}