Skip to main content

clt_database/
busy.rs

1use crate::MonotonicInstant;
2use std::time::Duration;
3
4/// Type alias for busy handler callback function.
5///
6/// The callback receives:
7/// - `count`: The number of times the busy handler has been invoked for the same locking event
8///
9/// Returns:
10/// - `0` to stop retrying and return SQLITE_BUSY to the application.
11/// - Non-zero to retry the database access.
12///
13/// # Safety Notes (per SQLite spec)
14/// - The callback MUST NOT modify the database connection that invoked it.
15/// - The callback MUST NOT close the connection or any prepared statement.
16/// - The callback is NOT reentrant.
17pub type BusyHandlerCallback = Box<dyn Fn(i32) -> i32 + Send + Sync>;
18
19#[derive(Default)]
20/// Represents the busy handler configuration for a connection.
21pub enum BusyHandler {
22    #[default]
23    /// No busy handler: return SQLITE_BUSY immediately on lock contention.
24    None,
25    /// Default timeout-based handler (implements sqliteDefaultBusyCallback)
26    /// The duration is the maximum total time to wait before giving up
27    Timeout(Duration),
28    /// Custom user-defined callback handler
29    Custom { callback: BusyHandlerCallback },
30}
31
32impl std::fmt::Debug for BusyHandler {
33    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34        match self {
35            BusyHandler::None => write!(f, "BusyHandler::None"),
36            BusyHandler::Timeout(d) => write!(f, "BusyHandler::Timeout({d:?}"),
37            BusyHandler::Custom { .. } => write!(f, "BusyHandler::Custom"),
38        }
39    }
40}
41
42/// Tracks the state of busy handler invocations for a statement.
43///
44/// This implements a yield-based busy handling mechanism that integrates with
45/// the async event loop. Instead of blocking with `thread::sleep`, the statement
46/// yields back to the caller with `StepResult::IO` and a timeout. When `step()`
47/// is called again after the timeout has passed, it retries the operation.
48///
49/// Uses increasing delays. After 12 iterations, continues with 100ms delays until max duration is reached.
50#[derive(Debug)]
51pub struct BusyHandlerState {
52    /// Number of times the busy handler has been invoked for this locking event
53    invocation_count: i32,
54    /// For timeout-based handlers: the next timeout instant to wait until
55    timeout: MonotonicInstant,
56    /// For timeout-based handlers: the current iteration index into DELAYS
57    iteration: usize,
58}
59
60impl BusyHandlerState {
61    /// Delay schedule for timeout-based busy handler (sqliteDefaultBusyCallback)
62    const DELAYS: [Duration; 12] = [
63        Duration::from_millis(1),
64        Duration::from_millis(2),
65        Duration::from_millis(5),
66        Duration::from_millis(10),
67        Duration::from_millis(15),
68        Duration::from_millis(20),
69        Duration::from_millis(25),
70        Duration::from_millis(25),
71        Duration::from_millis(25),
72        Duration::from_millis(50),
73        Duration::from_millis(50),
74        Duration::from_millis(100),
75    ];
76
77    /// Cumulative totals for each iteration (for calculating remaining time)
78    const TOTALS: [Duration; 12] = [
79        Duration::from_millis(0),
80        Duration::from_millis(1),
81        Duration::from_millis(3),
82        Duration::from_millis(8),
83        Duration::from_millis(18),
84        Duration::from_millis(33),
85        Duration::from_millis(53),
86        Duration::from_millis(78),
87        Duration::from_millis(103),
88        Duration::from_millis(128),
89        Duration::from_millis(178),
90        Duration::from_millis(228),
91    ];
92
93    /// Create a new busy handler state
94    pub fn new(now: MonotonicInstant) -> Self {
95        Self {
96            invocation_count: 0,
97            timeout: now,
98            iteration: 0,
99        }
100    }
101
102    /// Reset the state for a new locking event
103    pub fn reset(&mut self, now: MonotonicInstant) {
104        self.invocation_count = 0;
105        self.timeout = now;
106        self.iteration = 0;
107    }
108
109    /// Get the current timeout instant
110    pub fn timeout(&self) -> MonotonicInstant {
111        self.timeout
112    }
113
114    /// Invoke the busy handler and determine whether to retry.
115    ///
116    /// Returns `true` if the operation should be retried, `false` if SQLITE_BUSY
117    /// should be returned to the application.
118    ///
119    /// For timeout-based handlers, this also updates the internal timeout instant.
120    /// For custom handlers, this invokes the callback and respects its return value.
121    pub fn invoke(&mut self, handler: &BusyHandler, now: MonotonicInstant) -> bool {
122        match handler {
123            BusyHandler::None => {
124                // No handler: return BUSY immediately
125                false
126            }
127            BusyHandler::Timeout(max_duration) => self.invoke_timeout_handler(*max_duration, now),
128            BusyHandler::Custom { callback } => {
129                let result = callback(self.invocation_count);
130                self.invocation_count += 1;
131                if result != 0 {
132                    // Retry with a small delay
133                    self.timeout = now + Duration::from_millis(1);
134                    true
135                } else {
136                    false
137                }
138            }
139        }
140    }
141
142    /// Implements sqliteDefaultBusyCallback logic for timeout-based handling.
143    ///
144    /// This uses an exponentially increasing delay schedule, capped at 100ms per iteration.
145    fn invoke_timeout_handler(&mut self, max_duration: Duration, now: MonotonicInstant) -> bool {
146        let idx = self.iteration.min(11);
147        let mut delay = Self::DELAYS[idx];
148        let mut prior = Self::TOTALS[idx];
149
150        // After 12 iterations, each additional iteration adds 100ms
151        if self.iteration >= 12 {
152            prior += delay * (self.iteration as u32 - 11);
153        }
154
155        // Check if we've exceeded or would exceed the max duration
156        if prior + delay > max_duration {
157            delay = max_duration.saturating_sub(prior);
158            if delay.is_zero() {
159                return false;
160            }
161        }
162
163        self.iteration = self.iteration.saturating_add(1);
164        self.invocation_count += 1;
165        self.timeout = now + delay;
166        true
167    }
168
169    /// Get the delay duration that should be waited before the next retry.
170    ///
171    /// This returns the duration between `now` and the timeout instant.
172    /// Returns `Duration::ZERO` if the timeout has already passed.
173    pub fn get_delay(&self, now: MonotonicInstant) -> Duration {
174        if now >= self.timeout {
175            Duration::ZERO
176        } else {
177            self.timeout.duration_since(now)
178        }
179    }
180}
181
182#[cfg(clt_turso_tests)]
183mod tests {
184    use super::*;
185
186    fn test_instant() -> MonotonicInstant {
187        MonotonicInstant::now()
188    }
189
190    #[test]
191    fn test_busy_handler_timeout_basic() {
192        let handler = BusyHandler::Timeout(Duration::from_millis(100));
193        let now = test_instant();
194        let mut state = BusyHandlerState::new(now);
195
196        // First invocation should return true (retry)
197        assert!(state.invoke(&handler, now));
198        // Timeout should be set to 1ms from now
199        assert_eq!(state.timeout(), now + Duration::from_millis(1));
200    }
201
202    #[test]
203    fn test_busy_handler_timeout_exhausted() {
204        let handler = BusyHandler::Timeout(Duration::from_millis(0));
205        let now = test_instant();
206        let mut state = BusyHandlerState::new(now);
207
208        // Zero timeout should return false immediately
209        assert!(!state.invoke(&handler, now));
210    }
211
212    #[test]
213    fn test_busy_handler_custom_callback() {
214        // Callback that retries 3 times then gives up
215        let callback: BusyHandlerCallback = Box::new(|count| if count < 3 { 1 } else { 0 });
216        let handler = BusyHandler::Custom { callback };
217        let now = test_instant();
218        let mut state = BusyHandlerState::new(now);
219
220        // First 3 invocations should retry
221        assert!(state.invoke(&handler, now));
222        assert!(state.invoke(&handler, now));
223        assert!(state.invoke(&handler, now));
224        // 4th invocation should return false
225        assert!(!state.invoke(&handler, now));
226    }
227
228    #[test]
229    fn test_busy_handler_none_returns_false_immediately() {
230        let handler = BusyHandler::None;
231        let now = test_instant();
232        let mut state = BusyHandlerState::new(now);
233
234        // None handler should always return false (don't retry)
235        assert!(!state.invoke(&handler, now));
236        // Even on subsequent invocations
237        assert!(!state.invoke(&handler, now));
238    }
239
240    #[test]
241    fn test_custom_callback_receives_correct_count() {
242        use std::sync::{Arc, Mutex};
243
244        // Track the counts passed to callback (using Arc+Mutex for Send+Sync)
245        let counts = Arc::new(Mutex::new(Vec::new()));
246        let counts_clone = counts.clone();
247
248        let callback: BusyHandlerCallback = Box::new(move |count| {
249            counts_clone.lock().unwrap().push(count);
250            if count < 5 {
251                1
252            } else {
253                0
254            }
255        });
256
257        let handler = BusyHandler::Custom { callback };
258        let now = test_instant();
259        let mut state = BusyHandlerState::new(now);
260
261        // Invoke 6 times
262        for _ in 0..6 {
263            state.invoke(&handler, now);
264        }
265
266        // Verify counts were 0, 1, 2, 3, 4, 5
267        assert_eq!(*counts.lock().unwrap(), vec![0, 1, 2, 3, 4, 5]);
268    }
269
270    #[test]
271    fn test_custom_callback_always_retry() {
272        // Callback that always retries
273        let callback: BusyHandlerCallback = Box::new(|_| 1);
274        let handler = BusyHandler::Custom { callback };
275        let now = test_instant();
276        let mut state = BusyHandlerState::new(now);
277
278        // Should always return true
279        for _ in 0..100 {
280            assert!(state.invoke(&handler, now));
281        }
282    }
283
284    #[test]
285    fn test_custom_callback_never_retry() {
286        // Callback that never retries
287        let callback: BusyHandlerCallback = Box::new(|_| 0);
288        let handler = BusyHandler::Custom { callback };
289        let now = test_instant();
290        let mut state = BusyHandlerState::new(now);
291
292        // First invocation should return false
293        assert!(!state.invoke(&handler, now));
294    }
295
296    #[test]
297    fn test_custom_callback_sets_timeout() {
298        let callback: BusyHandlerCallback = Box::new(|_| 1);
299        let handler = BusyHandler::Custom { callback };
300        let now = test_instant();
301        let mut state = BusyHandlerState::new(now);
302
303        assert!(state.invoke(&handler, now));
304        // Custom callback sets 1ms timeout
305        assert_eq!(state.timeout(), now + Duration::from_millis(1));
306    }
307
308    #[test]
309    fn test_timeout_delay_schedule() {
310        let handler = BusyHandler::Timeout(Duration::from_secs(10)); // Long timeout
311        let now = test_instant();
312        let mut state = BusyHandlerState::new(now);
313
314        // Expected delays per iteration: 1, 2, 5, 10, 15, 20, 25, 25, 25, 50, 50, 100ms
315        // The timeout is set to `now + delay` each time, so we check individual delays
316        let expected_delays_ms: [u64; 12] = [1, 2, 5, 10, 15, 20, 25, 25, 25, 50, 50, 100];
317
318        for (i, expected_ms) in expected_delays_ms.iter().enumerate() {
319            assert!(state.invoke(&handler, now), "iteration {i} should retry");
320            let timeout = state.timeout();
321            assert_eq!(
322                timeout,
323                now + Duration::from_millis(*expected_ms),
324                "iteration {i} should have delay of {expected_ms}ms"
325            );
326        }
327    }
328
329    #[test]
330    fn test_timeout_caps_at_max_duration() {
331        // 5ms timeout - should only allow a few iterations
332        let handler = BusyHandler::Timeout(Duration::from_millis(5));
333        let now = test_instant();
334        let mut state = BusyHandlerState::new(now);
335
336        // First iteration: 1ms delay (total: 1ms)
337        assert!(state.invoke(&handler, now));
338        // Second iteration: 2ms delay (total: 3ms)
339        assert!(state.invoke(&handler, now));
340        // Third iteration: would be 5ms but only 2ms left (total would be 8ms > 5ms)
341        // So delay is capped to 2ms
342        assert!(state.invoke(&handler, now));
343        // Fourth iteration: no time left
344        assert!(!state.invoke(&handler, now));
345    }
346
347    #[test]
348    fn test_state_reset() {
349        let handler = BusyHandler::Timeout(Duration::from_millis(100));
350        let now = test_instant();
351        let mut state = BusyHandlerState::new(now);
352
353        // Invoke a few times
354        state.invoke(&handler, now);
355        state.invoke(&handler, now);
356        state.invoke(&handler, now);
357
358        // Reset
359        let later = MonotonicInstant::now();
360        state.reset(later);
361
362        // Should be back to initial state
363        assert_eq!(state.timeout(), later);
364        assert!(state.invoke(&handler, later));
365        // First delay after reset should be 1ms
366        assert_eq!(state.timeout(), later + Duration::from_millis(1));
367    }
368
369    #[test]
370    fn test_get_delay_when_timeout_passed() {
371        let now = MonotonicInstant::now();
372        let state = BusyHandlerState::new(now);
373
374        // Timeout is at `now`, so any time >= now should return zero delay
375        assert_eq!(state.get_delay(now), Duration::ZERO);
376
377        // A later time should also return zero
378        std::thread::sleep(Duration::from_micros(10));
379        let later = MonotonicInstant::now();
380        assert_eq!(state.get_delay(later), Duration::ZERO);
381    }
382
383    #[test]
384    fn test_get_delay_calculates_remaining_time() {
385        let now = MonotonicInstant::now();
386        let mut state = BusyHandlerState::new(now);
387
388        let handler = BusyHandler::Timeout(Duration::from_millis(100));
389        state.invoke(&handler, now); // Sets timeout to now + 1ms
390
391        // Check delay from `now` - should be 1ms
392        let delay = state.get_delay(now);
393        assert_eq!(delay, Duration::from_millis(1));
394    }
395}