1use std::sync::Arc;
4use std::sync::atomic::{AtomicBool, Ordering};
5use std::time::{Duration, Instant};
6
7use crate::error::{Error, Result};
8
9#[derive(Debug, Clone)]
33pub struct Cancel {
34 stopped: Arc<AtomicBool>,
35 started: Instant,
36 limit: Option<Duration>,
37}
38
39impl Default for Cancel {
40 fn default() -> Self {
41 Self::new()
42 }
43}
44
45impl Cancel {
46 #[must_use]
48 pub fn new() -> Self {
49 Self { stopped: Arc::new(AtomicBool::new(false)), started: Instant::now(), limit: None }
50 }
51
52 #[must_use]
54 pub fn after(timeout: Duration) -> Self {
55 Self {
56 stopped: Arc::new(AtomicBool::new(false)),
57 started: Instant::now(),
58 limit: Some(timeout),
59 }
60 }
61
62 #[must_use]
73 pub fn restart(&self, timeout: Option<Duration>) -> Self {
74 self.stopped.store(false, Ordering::Relaxed);
75 Self { stopped: Arc::clone(&self.stopped), started: Instant::now(), limit: timeout }
76 }
77
78 pub fn cancel(&self) {
83 self.stopped.store(true, Ordering::Relaxed);
84 }
85
86 #[must_use]
88 pub fn is_cancelled(&self) -> bool {
89 self.stopped.load(Ordering::Relaxed) || self.expired()
90 }
91
92 #[must_use]
94 pub fn elapsed(&self) -> Duration {
95 self.started.elapsed()
96 }
97
98 #[must_use]
100 pub fn limit(&self) -> Option<Duration> {
101 self.limit
102 }
103
104 pub fn check(&self) -> Result<()> {
115 if self.stopped.load(Ordering::Relaxed) {
116 return Err(Error::interrupt("Interrupted!"));
117 }
118 if self.expired() {
119 let limit = self.limit.unwrap_or_default();
120 return Err(Error::interrupt(format!(
121 "query took longer than the {} millisecond limit it was given",
122 limit.as_millis()
123 )));
124 }
125 Ok(())
126 }
127
128 fn expired(&self) -> bool {
133 self.limit.is_some_and(|limit| self.started.elapsed() >= limit)
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use std::thread;
140 use std::time::Duration;
141
142 use super::Cancel;
143
144 #[test]
145 fn a_fresh_token_lets_the_query_run() {
146 let cancel = Cancel::new();
147 assert!(!cancel.is_cancelled());
148 assert!(cancel.check().is_ok());
149 assert_eq!(cancel.limit(), None);
150 }
151
152 #[test]
153 fn cancelling_one_handle_stops_the_query_holding_another() {
154 let cancel = Cancel::new();
155 let other = cancel.clone();
156 other.cancel();
157 assert!(cancel.is_cancelled());
158 let error = cancel.check().expect_err("it was cancelled");
159 assert_eq!(error.code().duckdb_name(), "Interrupt Error");
160 assert_eq!(error.message(), "Interrupted!");
161 }
162
163 #[test]
164 fn a_token_another_thread_cancels_is_seen_by_the_one_running_the_query() {
165 let cancel = Cancel::new();
166 let other = cancel.clone();
167 let stopper = thread::spawn(move || other.cancel());
168 stopper.join().expect("the thread ran");
169 assert!(cancel.is_cancelled());
170 }
171
172 #[test]
173 fn a_time_limit_runs_out_on_its_own() {
174 let cancel = Cancel::after(Duration::from_millis(1));
175 assert_eq!(cancel.limit(), Some(Duration::from_millis(1)));
176 thread::sleep(Duration::from_millis(5));
177 assert!(cancel.is_cancelled());
178 let error = cancel.check().expect_err("the time is up");
179 assert_eq!(error.code().duckdb_name(), "Interrupt Error");
180 assert!(error.message().contains("longer than the 1 millisecond limit"), "{error}");
181 }
182
183 #[test]
184 fn a_timeout_and_an_interrupt_say_different_things() {
185 let interrupted = Cancel::new();
188 interrupted.cancel();
189 let timed_out = Cancel::after(Duration::from_millis(0));
190 assert_ne!(
191 interrupted.check().expect_err("cancelled").message(),
192 timed_out.check().expect_err("timed out").message()
193 );
194 }
195
196 #[test]
197 fn restarting_shares_the_flag_and_starts_the_clock_again() {
198 let connection = Cancel::new();
199 let statement = connection.restart(Some(Duration::from_secs(60)));
200 assert!(!statement.is_cancelled());
201 connection.cancel();
202 assert!(statement.is_cancelled(), "the flag is shared");
203 }
204
205 #[test]
206 fn an_interrupt_between_two_statements_does_not_stop_the_next_one() {
207 let connection = Cancel::new();
208 connection.cancel();
209 let statement = connection.restart(None);
210 assert!(!statement.is_cancelled());
211 assert!(!connection.is_cancelled(), "and the connection is usable again");
212 }
213
214 #[test]
215 fn a_limit_is_on_the_statement_rather_than_on_the_connection() {
216 let connection = Cancel::after(Duration::from_millis(1));
217 thread::sleep(Duration::from_millis(5));
218 assert!(connection.is_cancelled());
219 let statement = connection.restart(Some(Duration::from_secs(60)));
220 assert!(!statement.is_cancelled(), "the clock started again");
221 }
222}