query_runtime/runtime/context/
cancellation.rs1use std::time::{Duration, Instant};
4
5use query_planning::error::{QueryError, QueryErrorCode, Result};
6use tokio::time::sleep_until;
7use tokio_util::sync::{CancellationToken, WaitForCancellationFuture};
8
9use super::ExecutionContext;
10use crate::{
11 observability::{PlanExecutionId, RuntimeExecutionStageContext, RuntimeObservationHandle, RuntimeSpanId},
12 runtime::deadline::block_until,
13};
14
15#[derive(Debug, Clone)]
23pub struct ExecutionCancellation {
24 token: CancellationToken,
25 deadline: Option<Instant>,
26}
27
28impl ExecutionCancellation {
29 pub fn new() -> Self {
31 Self::with_deadline(None)
32 }
33
34 pub fn with_deadline(deadline: Option<Instant>) -> Self {
41 Self { token: CancellationToken::new(), deadline }
42 }
43
44 pub fn cancel(&self) {
47 self.token.cancel();
48 }
49
50 pub async fn cancelled_or_expired(&self) {
55 match self.deadline {
56 Some(deadline) => tokio::select! {
57 () = self.token.cancelled() => {},
58 () = sleep_until(deadline.into()) => {},
59 },
60 None => self.token.cancelled().await,
61 }
62 }
63
64 pub async fn terminated(&self, operation: &'static str) -> QueryError {
69 self.cancelled_or_expired().await;
70 match self.check(operation) {
71 Err(error) => error,
72 Ok(()) => deadline_expired_error(operation),
73 }
74 }
75
76 pub(crate) fn cancel_requested(&self) -> WaitForCancellationFuture<'_> {
80 self.token.cancelled()
81 }
82
83 pub fn sleep(&self, duration: Duration, operation: &'static str) -> Result<()> {
87 let until = Instant::now() + duration;
88 let until = self.deadline.map_or(until, |deadline| deadline.min(until));
89 block_until(self.cancel_requested(), Some(until));
90 self.check(operation)
91 }
92
93 pub fn is_cancelled(&self) -> bool {
101 self.cancellation_requested() || self.is_expired()
102 }
103
104 pub fn deadline(&self) -> Option<Instant> {
106 self.deadline
107 }
108
109 pub fn is_expired(&self) -> bool {
111 self.deadline.is_some_and(|deadline| Instant::now() >= deadline)
112 }
113
114 pub fn check(&self, operation: &'static str) -> Result<()> {
117 if self.cancellation_requested() {
118 return Err(QueryError::new(QueryErrorCode::Cancelled, operation, "native query execution was cancelled")
119 .with_fact("cancellation_requested", true));
120 }
121 if self.is_expired() {
122 return Err(deadline_expired_error(operation));
123 }
124 Ok(())
125 }
126
127 fn cancellation_requested(&self) -> bool {
128 self.token.is_cancelled()
129 }
130}
131
132fn deadline_expired_error(operation: &'static str) -> QueryError {
133 QueryError::new(QueryErrorCode::Cancelled, operation, "native query execution deadline expired")
134 .with_fact("cancellation_requested", true)
135 .with_fact("reason", "deadline_expired")
136}
137
138impl Default for ExecutionCancellation {
139 fn default() -> Self {
140 Self::new()
141 }
142}
143
144impl RuntimeExecutionStageContext for ExecutionContext {
145 fn runtime_operator_stage_context(
146 &self,
147 ) -> (RuntimeObservationHandle, Option<PlanExecutionId>, Option<RuntimeSpanId>) {
148 let observation = self.runtime_observation().clone();
149 let (plan_execution_id, parent_span_id) = self
150 .runtime_execution_scope()
151 .map(|scope| (Some(scope.plan_execution_id().clone()), Some(scope.plan_span_id())))
152 .unwrap_or((None, None));
153 (observation, plan_execution_id, parent_span_id)
154 }
155}