use std::sync::Arc;
use crate::meta_storage::{MetaBackend, MetaClientApi, MetaConnApi, MetaTxApi};
use crate::scheduler::context::SchedulerContext;
use crate::scheduler::events::{ControlEventReceiver, SchedulerStateSender};
use crate::scheduler::states::{InitTransition, NextState, SchedulerState};
use crate::scheduler::{SchedulerError, SchedulerHandler};
use crate::schema::{RunFootprint, RunState, SharedProgressMap, TableShape};
use crate::service::OperonService;
use crate::storage::OperonStorage;
use crate::ui::UiMode;
type SchedulerResult<T, UErr, SErr, MErr> = Result<T, SchedulerError<UErr, SErr, MErr>>;
type BoxedState<UErr, SErr, MErr> =
Box<dyn SchedulerState<Error = SchedulerError<UErr, SErr, MErr>>>;
pub(crate) struct Scheduler<Svc, Sto, MSto>
where
Svc: OperonService,
Sto: OperonStorage,
MSto: MetaBackend,
{
ctx: SchedulerContext<Svc, Sto, MSto>,
ctrl_rx: ControlEventReceiver,
channel_size: usize,
ui_mode: UiMode,
}
#[allow(clippy::too_many_arguments)]
impl<Svc, Sto, MSto> Scheduler<Svc, Sto, MSto>
where
Sto: OperonStorage,
Svc: OperonService,
MSto: MetaBackend,
{
pub(crate) fn new(
service: Arc<Svc>,
storage: Arc<Sto>,
meta_storage: MSto,
handler: SchedulerHandler<Svc, Sto, MSto>,
progresses: SharedProgressMap,
ctrl_rx: ControlEventReceiver,
channel_size: usize,
ui_mode: UiMode,
) -> Self {
handler.validate_pool_sizes();
let ctx = SchedulerContext {
service,
storage,
meta_storage,
handler,
progresses,
};
Self {
ctx,
ctrl_rx,
channel_size,
ui_mode,
}
}
pub(crate) async fn work_and_send(self, sched_tx: SchedulerStateSender) {
let result = self.work().await;
let _send_result = sched_tx.send(result.map_err(|e| e.to_string()));
}
async fn work(mut self) -> SchedulerResult<bool, Svc::Error, Sto::Error, MSto::Error> {
self.ctx.meta_storage.ensure_lock().await?;
self.ctx.storage.init().await?;
let shape_changed = self.init_meta_storage().await?.is_stale;
if shape_changed {
self.abort_recorded_run().await?;
}
let heartbeat_handle = self.ctx.meta_storage.clone();
let mut state: BoxedState<Svc::Error, Sto::Error, MSto::Error> = Box::new(
InitTransition::state(self.ctx, self.ui_mode, self.channel_size, shape_changed),
);
let heartbeat_period = std::time::Duration::from_secs(30);
let mut lock_heartbeat = tokio::time::interval_at(
tokio::time::Instant::now() + heartbeat_period,
heartbeat_period,
);
loop {
let next = tokio::select! {
result = state.wait_progress() => {
result?;
state.handle_progress().await?
}
Some(evt) = self.ctrl_rx.recv() => state.handle_control_event(evt).await?,
_ = lock_heartbeat.tick() => {
heartbeat_handle.check_lock().await?;
continue;
}
};
match next {
NextState::Next(new_state) => state = new_state,
NextState::Exit { exit_ui } => {
return Ok(exit_ui);
}
}
}
}
async fn init_meta_storage(
&self,
) -> SchedulerResult<TableShape, Svc::Error, Sto::Error, MSto::Error> {
let mut conn = self.ctx.meta_storage.scheduler_conn().await?;
let tx = conn.transaction().await?;
let shape = self.ctx.handler.init_meta_storage(tx.as_client()).await?;
tx.commit().await?;
Ok(shape)
}
async fn abort_recorded_run(&self) -> SchedulerResult<(), Svc::Error, Sto::Error, MSto::Error> {
let conn = self.ctx.meta_storage.scheduler_conn().await?;
let Some(recorded) = conn.as_client().get_footprint().await? else {
return Ok(());
};
let footprint = RunFootprint::new(recorded.metadata.run_id, RunState::Aborted);
self.ctx.storage.put_footprint(&footprint).await?;
conn.as_client().upsert_run(&footprint).await?;
Ok(())
}
}