use std::time::{Duration, Instant};
use query_planning::error::{QueryError, QueryErrorCode, Result};
use tokio::time::sleep_until;
use tokio_util::sync::{CancellationToken, WaitForCancellationFuture};
use super::ExecutionContext;
use crate::{
observability::{PlanExecutionId, RuntimeExecutionStageContext, RuntimeObservationHandle, RuntimeSpanId},
runtime::deadline::block_until,
};
#[derive(Debug, Clone)]
pub struct ExecutionCancellation {
token: CancellationToken,
deadline: Option<Instant>,
}
impl ExecutionCancellation {
pub fn new() -> Self {
Self::with_deadline(None)
}
pub fn with_deadline(deadline: Option<Instant>) -> Self {
Self { token: CancellationToken::new(), deadline }
}
pub fn cancel(&self) {
self.token.cancel();
}
pub async fn cancelled_or_expired(&self) {
match self.deadline {
Some(deadline) => tokio::select! {
() = self.token.cancelled() => {},
() = sleep_until(deadline.into()) => {},
},
None => self.token.cancelled().await,
}
}
pub async fn terminated(&self, operation: &'static str) -> QueryError {
self.cancelled_or_expired().await;
match self.check(operation) {
Err(error) => error,
Ok(()) => deadline_expired_error(operation),
}
}
pub(crate) fn cancel_requested(&self) -> WaitForCancellationFuture<'_> {
self.token.cancelled()
}
pub fn sleep(&self, duration: Duration, operation: &'static str) -> Result<()> {
let until = Instant::now() + duration;
let until = self.deadline.map_or(until, |deadline| deadline.min(until));
block_until(self.cancel_requested(), Some(until));
self.check(operation)
}
pub fn is_cancelled(&self) -> bool {
self.cancellation_requested() || self.is_expired()
}
pub fn deadline(&self) -> Option<Instant> {
self.deadline
}
pub fn is_expired(&self) -> bool {
self.deadline.is_some_and(|deadline| Instant::now() >= deadline)
}
pub fn check(&self, operation: &'static str) -> Result<()> {
if self.cancellation_requested() {
return Err(QueryError::new(QueryErrorCode::Cancelled, operation, "native query execution was cancelled")
.with_fact("cancellation_requested", true));
}
if self.is_expired() {
return Err(deadline_expired_error(operation));
}
Ok(())
}
fn cancellation_requested(&self) -> bool {
self.token.is_cancelled()
}
}
fn deadline_expired_error(operation: &'static str) -> QueryError {
QueryError::new(QueryErrorCode::Cancelled, operation, "native query execution deadline expired")
.with_fact("cancellation_requested", true)
.with_fact("reason", "deadline_expired")
}
impl Default for ExecutionCancellation {
fn default() -> Self {
Self::new()
}
}
impl RuntimeExecutionStageContext for ExecutionContext {
fn runtime_operator_stage_context(
&self,
) -> (RuntimeObservationHandle, Option<PlanExecutionId>, Option<RuntimeSpanId>) {
let observation = self.runtime_observation().clone();
let (plan_execution_id, parent_span_id) = self
.runtime_execution_scope()
.map(|scope| (Some(scope.plan_execution_id().clone()), Some(scope.plan_span_id())))
.unwrap_or((None, None));
(observation, plan_execution_id, parent_span_id)
}
}