use std::sync::Arc;
use std::time::Duration;
use futures_util::StreamExt;
use serde_json::Value;
use tokio::sync::Mutex;
use tokio::task::JoinHandle;
use crate::context::CancelSignal;
use super::error::StoreError;
use super::model::{
Job, JobId, LogRange, PipelineId, PipelineSpec, Run, RunId, RunOutcome, RunStatus,
};
use super::registry::HandlerRegistry;
use super::store::{Store, StoreEvent};
use super::worker::{cancel_sweep, dispatcher, orphan};
#[derive(Clone, Debug)]
pub struct RuntimeConfig {
pub dispatch_tick: Duration,
pub cancel_sweep_tick: Duration,
pub orphan_tick: Duration,
pub orphan_max_runtime: Option<Duration>,
}
impl Default for RuntimeConfig {
fn default() -> Self {
Self {
dispatch_tick: Duration::from_millis(25),
cancel_sweep_tick: Duration::from_millis(50),
orphan_tick: Duration::from_secs(5),
orphan_max_runtime: Some(Duration::from_secs(300)),
}
}
}
#[derive(Clone)]
pub struct Runtime {
store: Arc<dyn Store>,
registry: HandlerRegistry,
config: RuntimeConfig,
cancel: CancelSignal,
handles: Arc<Mutex<Vec<JoinHandle<()>>>>,
}
impl Runtime {
pub fn new(store: Arc<dyn Store>, registry: HandlerRegistry, config: RuntimeConfig) -> Self {
Self {
store,
registry,
config,
cancel: CancelSignal::new(),
handles: Arc::new(Mutex::new(Vec::new())),
}
}
pub fn with_defaults(store: Arc<dyn Store>, registry: HandlerRegistry) -> Self {
Self::new(store, registry, RuntimeConfig::default())
}
pub fn store(&self) -> &Arc<dyn Store> {
&self.store
}
pub fn registry(&self) -> &HandlerRegistry {
&self.registry
}
pub async fn put_pipeline(&self, spec: PipelineSpec) -> Result<PipelineId, StoreError> {
let id = spec.id;
self.store.put_pipeline(spec).await?;
Ok(id)
}
pub async fn get_pipeline(&self, id: PipelineId) -> Result<Option<PipelineSpec>, StoreError> {
self.store.get_pipeline(id).await
}
pub async fn submit(&self, pipeline: PipelineId, inputs: Value) -> Result<RunId, StoreError> {
self.store.create_run(pipeline, inputs).await
}
pub async fn wait(&self, run_id: RunId) -> Result<RunOutcome, StoreError> {
if let Some(run) = self.store.get_run(run_id).await? {
if matches!(
run.status,
RunStatus::Completed | RunStatus::Failed | RunStatus::Cancelled
) {
return Ok(RunOutcome {
status: run.status,
jobs: self.store.list_run_jobs(run_id).await?,
});
}
}
let mut events = self.store.subscribe().await;
if let Some(run) = self.store.get_run(run_id).await? {
if matches!(
run.status,
RunStatus::Completed | RunStatus::Failed | RunStatus::Cancelled
) {
return Ok(RunOutcome {
status: run.status,
jobs: self.store.list_run_jobs(run_id).await?,
});
}
}
while let Some(ev) = events.next().await {
match ev {
StoreEvent::RunTerminal { run_id: r, status }
if r == run_id
&& matches!(
status,
RunStatus::Completed | RunStatus::Failed | RunStatus::Cancelled
) =>
{
return Ok(RunOutcome {
status,
jobs: self.store.list_run_jobs(run_id).await?,
});
}
_ => {}
}
}
Err(StoreError::Backend("event stream closed".into()))
}
pub async fn cancel(&self, run_id: RunId) -> Result<(), StoreError> {
self.store.cancel_run(run_id).await
}
pub async fn read_log(
&self,
job_id: JobId,
range: LogRange,
) -> Result<Vec<String>, StoreError> {
self.store.read_log(job_id, range).await
}
pub async fn get_job(&self, job_id: JobId) -> Result<Option<Job>, StoreError> {
self.store.get_job(job_id).await
}
pub async fn get_run(&self, run_id: RunId) -> Result<Option<Run>, StoreError> {
self.store.get_run(run_id).await
}
pub async fn list_run_jobs(&self, run_id: RunId) -> Result<Vec<Job>, StoreError> {
self.store.list_run_jobs(run_id).await
}
pub async fn start(&self) {
let mut h = self.handles.lock().await;
if !h.is_empty() {
return;
}
let store = self.store.clone();
let registry = self.registry.clone();
let cfg = self.config.clone();
let cancel = self.cancel.clone();
let s1 = store.clone();
let r1 = registry.clone();
let cancel1 = cancel.clone();
let dispatch_tick = cfg.dispatch_tick;
h.push(tokio::spawn(async move {
dispatcher::run_dispatcher(s1, r1, dispatch_tick, cancel1).await;
}));
let s2 = store.clone();
let r2 = registry.clone();
let cancel2 = cancel.clone();
let sweep_tick = cfg.cancel_sweep_tick;
h.push(tokio::spawn(async move {
cancel_sweep::run_cancel_sweep(s2, r2, sweep_tick, cancel2).await;
}));
if let Some(max_runtime) = cfg.orphan_max_runtime {
let s3 = store.clone();
let cancel3 = cancel.clone();
let orphan_tick = cfg.orphan_tick;
h.push(tokio::spawn(async move {
orphan::run_orphan(s3, max_runtime, orphan_tick, cancel3).await;
}));
}
}
pub async fn shutdown(&self) {
self.cancel.cancel();
let handles: Vec<_> = std::mem::take(&mut *self.handles.lock().await);
for h in handles {
let _ = h.await;
}
}
}