use chrono::{DateTime, Utc};
use concepts::{
prefixed_ulid::ExecutionId,
storage::{CancelOutcome, DbConnection, DbErrorWrite},
};
use executor::AbortOnDropHandle;
use std::{
sync::{Arc, Mutex},
time::Duration,
};
use tokio::sync::oneshot;
use tracing::{Instrument, debug, info, info_span};
#[derive(Clone)]
pub struct CancelRegistry {
tokens: Arc<Mutex<hashbrown::HashMap<ExecutionId, ActivityInfo>>>,
}
struct ActivityInfo {
interrupt_sender: oneshot::Sender<()>,
}
impl Default for CancelRegistry {
fn default() -> Self {
Self::new()
}
}
impl CancelRegistry {
#[must_use]
pub fn new() -> CancelRegistry {
CancelRegistry {
tokens: Arc::default(),
}
}
pub fn spawn_cancel_watcher(&self, sleep_duration: Duration) -> AbortOnDropHandle {
let clone = self.clone();
AbortOnDropHandle::new(
tokio::spawn({
async move {
debug!("Spawned the cancel watcher");
loop {
clone.tick();
tokio::time::sleep(sleep_duration).await;
}
}
.instrument(info_span!(parent: None, "cancel_watcher"))
})
.abort_handle(),
)
}
fn tick(&self) {
let mut guard = self.tokens.lock().unwrap();
guard.retain(|_exe, info| !info.interrupt_sender.is_closed());
}
pub(crate) fn activity_obtain_interrupt_token(
&self,
execution_id: ExecutionId,
) -> oneshot::Receiver<()> {
let mut guard = self.tokens.lock().unwrap();
let (interrupt_sender, receiver) = oneshot::channel();
guard.insert(execution_id, ActivityInfo { interrupt_sender });
receiver
}
pub async fn cancel_activity(
&self,
db_connection: &dyn DbConnection,
execution_id: &ExecutionId,
cancelled_at: DateTime<Utc>,
) -> Result<CancelOutcome, DbErrorWrite> {
info!(%execution_id, "Cancelling activity");
let outcome = db_connection
.cancel_activity_with_retries(execution_id, cancelled_at)
.await?;
if outcome == CancelOutcome::Cancelled {
let info = {
let mut guard = self.tokens.lock().unwrap();
guard.remove(execution_id)
};
if let Some(info) = info {
let _ = info.interrupt_sender.send(());
}
}
Ok(outcome)
}
}