1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
//! Timeout context for query execution
//!
//! This module provides a lightweight timeout checking mechanism that can be
//! passed to query execution functions (joins, scans, etc.) without requiring
//! the full `SelectExecutor` reference.
//!
//! ## Design Rationale
//!
//! The `SelectExecutor` owns the timeout configuration but many query execution
//! functions (especially joins) don't have access to it. Rather than threading
//! the executor through all call chains, we extract just the timeout-related
//! state into this lightweight struct.
//!
//! ## Usage Pattern
//!
//! ```text
//! // Create from executor
//! let timeout_ctx = TimeoutContext::from_executor(executor);
//!
//! // Pass to join/scan functions
//! nested_loop_join(left, right, condition, database, &timeout_ctx)?;
//!
//! // Check periodically in hot loops
//! for row in rows {
//! if iteration % CHECK_INTERVAL == 0 {
//! timeout_ctx.check()?;
//! }
//! // ... process row ...
//! }
//! ```
use instant::Instant;
use crate::{errors::ExecutorError, limits::MAX_QUERY_EXECUTION_SECONDS};
/// Recommended interval for timeout checks in hot loops.
/// Checking every 1000 iterations balances responsiveness with overhead.
pub const CHECK_INTERVAL: usize = 1000;
/// Lightweight timeout context for query execution.
///
/// This struct captures the timeout configuration from `SelectExecutor` and can
/// be passed to functions that need to check for timeouts but don't need the
/// full executor reference.
#[derive(Clone, Copy)]
pub struct TimeoutContext {
/// When query execution started
start_time: Instant,
/// Maximum execution time in seconds
timeout_seconds: u64,
}
impl TimeoutContext {
/// Create a new timeout context with the given start time and timeout.
pub fn new(start_time: Instant, timeout_seconds: u64) -> Self {
Self { start_time, timeout_seconds }
}
/// Create a timeout context from a `SelectExecutor`.
pub fn from_executor(executor: &crate::SelectExecutor<'_>) -> Self {
Self { start_time: executor.start_time, timeout_seconds: executor.timeout_seconds }
}
/// Create a default timeout context (for use when no executor is available).
///
/// This creates a context with the current time as start and default timeout.
/// Use sparingly - prefer `from_executor` when an executor is available.
pub fn new_default() -> Self {
Self { start_time: Instant::now(), timeout_seconds: MAX_QUERY_EXECUTION_SECONDS }
}
/// Check if the timeout has been exceeded.
///
/// Returns `Ok(())` if within timeout, `Err(QueryTimeoutExceeded)` if exceeded.
///
/// Call this periodically in hot loops (every `CHECK_INTERVAL` iterations).
#[inline]
pub fn check(&self) -> Result<(), ExecutorError> {
let elapsed = self.start_time.elapsed().as_secs();
if elapsed >= self.timeout_seconds {
return Err(ExecutorError::QueryTimeoutExceeded {
elapsed_seconds: elapsed,
max_seconds: self.timeout_seconds,
});
}
Ok(())
}
/// Get the start time for this timeout context.
pub fn start_time(&self) -> Instant {
self.start_time
}
/// Get the timeout in seconds for this timeout context.
pub fn timeout_seconds(&self) -> u64 {
self.timeout_seconds
}
}
impl Default for TimeoutContext {
fn default() -> Self {
Self::new_default()
}
}
#[cfg(test)]
mod tests {
use std::{thread::sleep, time::Duration};
use super::*;
#[test]
fn test_timeout_check_passes_within_limit() {
let ctx = TimeoutContext::new(Instant::now(), 60);
assert!(ctx.check().is_ok());
}
#[test]
fn test_timeout_check_fails_after_timeout() {
// Use a very short timeout
let ctx = TimeoutContext::new(Instant::now(), 0);
// Sleep briefly to ensure we're past the 0-second timeout
sleep(Duration::from_millis(10));
let result = ctx.check();
assert!(result.is_err());
if let Err(ExecutorError::QueryTimeoutExceeded { .. }) = result {
// Expected
} else {
panic!("Expected QueryTimeoutExceeded error");
}
}
#[test]
fn test_default_timeout_context() {
let ctx = TimeoutContext::default();
assert_eq!(ctx.timeout_seconds(), MAX_QUERY_EXECUTION_SECONDS);
assert!(ctx.check().is_ok());
}
}