use std::{
cell::Cell,
panic::AssertUnwindSafe,
rc::Rc,
time::{Duration, Instant},
};
use futures::{
channel::oneshot,
future::{AbortHandle, Abortable, FutureExt},
};
use lenso_app_plan::{PlanResolutionError, ResolvedAppPlan};
use lenso_kernel::{
DriverTask, ExecutionAdapterCatalog, LocalTask, PlanValidationError, RuntimeDriver,
ShutdownOutcome, TaskOutcome, TerminalOutcome,
};
use tokio::sync::Notify;
mod replicated;
pub use replicated::{
CrossLaneRequestCatalog, LaneCancellationToken, LaneDiagnosticsSnapshot, LaneInvocationOptions,
ReplicatedNativeApp, ReplicatedRunnerError,
};
#[derive(Clone, Debug)]
pub struct TokioDriver {
started_at: Instant,
shutdown_requested: Rc<Cell<bool>>,
runtime_event: Rc<Notify>,
jitter_state: Rc<Cell<u64>>,
}
impl TokioDriver {
pub fn new() -> Self {
Self::with_epoch(Instant::now())
}
pub(crate) fn with_epoch(started_at: Instant) -> Self {
Self {
started_at,
shutdown_requested: Rc::new(Cell::new(false)),
runtime_event: Rc::new(Notify::new()),
jitter_state: Rc::new(Cell::new(
u64::try_from(started_at.elapsed().as_nanos()).unwrap_or(u64::MAX)
^ 0x9e37_79b9_7f4a_7c15,
)),
}
}
pub fn request_shutdown(&self) {
self.shutdown_requested.set(true);
self.runtime_event.notify_one();
}
}
impl Default for TokioDriver {
fn default() -> Self {
Self::new()
}
}
impl RuntimeDriver for TokioDriver {
fn now(&self) -> Duration {
self.started_at.elapsed()
}
fn sleep_until(&self, deadline: Duration) -> futures::future::LocalBoxFuture<'static, ()> {
let target = self.started_at + deadline;
Box::pin(async move {
tokio::time::sleep_until(tokio::time::Instant::from_std(target)).await;
})
}
fn yield_now(&self) -> futures::future::LocalBoxFuture<'static, ()> {
Box::pin(tokio::task::yield_now())
}
fn wait_for_runtime_event(
&self,
deadline: Duration,
) -> futures::future::LocalBoxFuture<'static, ()> {
let target = self.started_at + deadline;
let runtime_event = Rc::clone(&self.runtime_event);
Box::pin(async move {
tokio::select! {
() = runtime_event.notified() => {}
() = tokio::time::sleep_until(tokio::time::Instant::from_std(target)) => {}
}
})
}
fn jitter(&self, maximum: Duration) -> Duration {
if maximum.is_zero() {
return Duration::ZERO;
}
let next = self
.jitter_state
.get()
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1_442_695_040_888_963_407);
self.jitter_state.set(next);
let maximum_nanos = maximum.as_nanos().min(u128::from(u64::MAX));
let jitter_nanos = u128::from(next) % maximum_nanos.saturating_add(1);
Duration::from_nanos(u64::try_from(jitter_nanos).unwrap_or(u64::MAX))
}
fn spawn_local(&self, task: LocalTask) -> Result<DriverTask, futures::task::SpawnError> {
let (abort, registration) = AbortHandle::new_pair();
let (completed, completion) = oneshot::channel();
tokio::task::spawn_local(async move {
let outcome = match AssertUnwindSafe(Abortable::new(task, registration))
.catch_unwind()
.await
{
Ok(Ok(())) => TaskOutcome::Completed,
Ok(Err(_)) => TaskOutcome::Cancelled,
Err(_) => TaskOutcome::Failed,
};
let _ = completed.send(outcome);
});
Ok(DriverTask::new(abort, completion))
}
fn shutdown_requested(&self) -> bool {
self.shutdown_requested.get()
}
}
pub async fn run<D: RuntimeDriver>(
plan: ResolvedAppPlan,
driver: D,
adapters: ExecutionAdapterCatalog,
shutdown_timeout: Duration,
) -> Result<TerminalOutcome, PlanValidationError> {
if let Err(error) = plan.validate() {
return Err(match error {
PlanResolutionError::UnsupportedSchemaVersion { expected, actual } => {
PlanValidationError::UnsupportedSchemaVersion { expected, actual }
}
error => PlanValidationError::InvalidResolvedPlan {
detail: error.to_string(),
},
});
}
let app = match lenso_kernel::Kernel::start(plan, driver.clone(), adapters).await {
Ok(app) => app,
Err(error) => return Ok(TerminalOutcome::StartupFailure { error }),
};
while !driver.shutdown_requested() && !app.is_failed() {
let failure_check = driver.now().saturating_add(Duration::from_millis(10));
driver.wait_for_runtime_event(failure_check).await;
}
if let Some(error) = app.terminal_failure() {
return Ok(match app.shutdown(shutdown_timeout).await {
ShutdownOutcome::Clean => TerminalOutcome::RuntimeFailure { error },
ShutdownOutcome::RuntimeFailure {
error: cleanup_error,
} => TerminalOutcome::RuntimeFailureDuringShutdown {
error,
cleanup_error,
},
ShutdownOutcome::Timeout => {
TerminalOutcome::RuntimeFailureWithShutdownTimeout { error }
}
});
}
Ok(match app.shutdown(shutdown_timeout).await {
ShutdownOutcome::Clean => TerminalOutcome::CleanShutdown,
ShutdownOutcome::RuntimeFailure { error } => TerminalOutcome::RuntimeFailure { error },
ShutdownOutcome::Timeout => TerminalOutcome::ShutdownTimeout,
})
}