pub use crate::runtime::ProgressState;
use awa_model::{AwaError, CallbackConfig, JobRow};
use sqlx::PgPool;
use std::any::Any;
use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Duration;
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct CallbackGuard {
pub id: uuid::Uuid,
}
impl CallbackGuard {
fn new(id: uuid::Uuid) -> Self {
Self { id }
}
pub fn id(&self) -> uuid::Uuid {
self.id
}
#[cfg(feature = "__python-bridge")]
#[doc(hidden)]
pub fn from_bridge_token(id: uuid::Uuid) -> Self {
Self::new(id)
}
}
#[doc(hidden)]
pub type CallbackToken = CallbackGuard;
pub struct JobContext {
pub job: JobRow,
cancelled: Arc<AtomicBool>,
state: Arc<HashMap<std::any::TypeId, Box<dyn Any + Send + Sync>>>,
pool: PgPool,
progress: Arc<std::sync::Mutex<ProgressState>>,
}
impl JobContext {
pub fn new(
job: JobRow,
cancelled: Arc<AtomicBool>,
state: Arc<HashMap<std::any::TypeId, Box<dyn Any + Send + Sync>>>,
pool: PgPool,
progress: Arc<std::sync::Mutex<ProgressState>>,
) -> Self {
Self {
job,
cancelled,
state,
pool,
progress,
}
}
pub fn is_cancelled(&self) -> bool {
self.cancelled.load(Ordering::SeqCst)
}
pub fn cancellation_flag(&self) -> Arc<AtomicBool> {
self.cancelled.clone()
}
pub fn cancel(&self) {
self.cancelled.store(true, Ordering::SeqCst);
}
pub fn extract<T: Any + Send + Sync + Clone>(&self) -> Option<T> {
self.state
.get(&std::any::TypeId::of::<T>())
.and_then(|v| v.downcast_ref::<T>())
.cloned()
}
pub fn pool(&self) -> &PgPool {
&self.pool
}
pub async fn register_callback(&self, timeout: Duration) -> Result<CallbackGuard, AwaError> {
let callback_id = awa_model::admin::register_callback(
&self.pool,
self.job.id,
self.job.run_lease,
timeout,
)
.await?;
Ok(CallbackGuard::new(callback_id))
}
pub async fn register_callback_with_config(
&self,
timeout: Duration,
config: &CallbackConfig,
) -> Result<CallbackGuard, AwaError> {
let callback_id = awa_model::admin::register_callback_with_config(
&self.pool,
self.job.id,
self.job.run_lease,
timeout,
config,
)
.await?;
Ok(CallbackGuard::new(callback_id))
}
pub fn set_progress(&self, percent: u8, message: &str) {
let mut guard = self.progress.lock().expect("progress lock poisoned");
guard.set_progress(percent, Some(message));
}
pub fn update_metadata(&self, updates: serde_json::Value) -> Result<(), AwaError> {
let obj = updates
.as_object()
.ok_or_else(|| AwaError::Validation("update_metadata requires a JSON object".into()))?;
let mut guard = self.progress.lock().expect("progress lock poisoned");
if !guard.merge_metadata(obj) {
return Err(AwaError::Validation(
"progress.metadata is not a JSON object; cannot merge".into(),
));
}
Ok(())
}
pub async fn flush_progress(&self) -> Result<(), AwaError> {
let (snapshot, target_generation) = {
let guard = self.progress.lock().expect("progress lock poisoned");
match guard.pending_snapshot() {
Some(pair) => pair,
None => return Ok(()),
}
};
let result = sqlx::query(
r#"
UPDATE awa.jobs_hot
SET progress = $2
WHERE id = $1 AND state = 'running' AND run_lease = $3
"#,
)
.bind(self.job.id)
.bind(&snapshot)
.bind(self.job.run_lease)
.execute(&self.pool)
.await?;
if result.rows_affected() == 0 {
return Ok(());
}
let mut guard = self.progress.lock().expect("progress lock poisoned");
guard.ack(target_generation);
Ok(())
}
pub fn progress_buffer(&self) -> Arc<std::sync::Mutex<ProgressState>> {
self.progress.clone()
}
}