Skip to main content

armdb/queue/
wait.rs

1use std::collections::HashMap;
2use std::future::Future;
3use std::pin::Pin;
4use std::sync::Arc;
5use std::sync::atomic::{AtomicU64, Ordering};
6use std::task::{Context, Poll, Waker};
7use std::thread::JoinHandle;
8use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
9
10use crate::error::{DbError, DbResult};
11use crate::shutdown::ShutdownSignal;
12use crate::sync::{Condvar, Mutex, cv_wait_timeout, lock};
13
14use super::{LIVE_CLASS, Queue, QueueHandle, QueueKey, QueueSpec};
15
16/// How long the wake worker parks when nothing is registered.
17///
18/// Only a bound on how long a *lost* notification could cost; every real wake
19/// comes through the condvar.
20const WORKER_IDLE_PARK: Duration = Duration::from_secs(60);
21
22/// Debug-only synchronization point inside the wake worker.
23///
24/// It is absent from release builds and is never configured by production
25/// code. The worker stops here after its top-of-loop shutdown check and before
26/// it parks, which is exactly the window in which a `shutdown` can issue its
27/// notification with nobody waiting on the condvar.
28#[cfg(all(debug_assertions, feature = "queue-test-support"))]
29#[doc(hidden)]
30pub mod test_support {
31    use std::sync::{Arc, Barrier, LazyLock, Mutex};
32
33    /// `(arrived, release)` — the worker signals the first and waits on the second.
34    type ParkPause = (Arc<Barrier>, Arc<Barrier>);
35
36    static PARK_PAUSE: LazyLock<Mutex<Option<ParkPause>>> = LazyLock::new(|| Mutex::new(None));
37
38    /// Arm the pause; returns `(arrived, release)`.
39    pub fn pause_wake_worker_before_park() -> ParkPause {
40        let pause = (Arc::new(Barrier::new(2)), Arc::new(Barrier::new(2)));
41        *PARK_PAUSE.lock().expect("queue wake pause lock") = Some(pause.clone());
42        pause
43    }
44
45    pub fn disarm_wake_worker_pause() {
46        *PARK_PAUSE.lock().expect("queue wake pause lock") = None;
47    }
48
49    pub(crate) fn wait_before_park() {
50        let pause = PARK_PAUSE
51            .lock()
52            .expect("queue wake pause lock")
53            .as_ref()
54            .cloned();
55        if let Some((arrived, release)) = pause {
56            arrived.wait();
57            release.wait();
58        }
59    }
60}
61
62pub(crate) trait QueueHeaderRow {
63    fn lease_deadline_ms(&self) -> u64;
64}
65
66/// Fresh result of scanning queue visibility and lease state.
67#[doc(hidden)]
68pub struct QueueScan {
69    pub(crate) due: bool,
70    pub(crate) delay: Option<Duration>,
71}
72
73impl QueueScan {
74    pub(crate) fn rows<I, R>(rows: I, wakeup_recheck: Duration) -> DbResult<Self>
75    where
76        I: IntoIterator<Item = (QueueKey, R)>,
77        R: QueueHeaderRow,
78    {
79        Self::rows_at(rows, wakeup_recheck, now_ms()?)
80    }
81
82    pub(crate) fn rows_at<I, R>(rows: I, wakeup_recheck: Duration, now_ms: u64) -> DbResult<Self>
83    where
84        I: IntoIterator<Item = (QueueKey, R)>,
85        R: QueueHeaderRow,
86    {
87        let mut next = None;
88        for (key, row) in rows {
89            if key.class() != LIVE_CLASS {
90                break;
91            }
92            if key.visible_at_ms() > now_ms {
93                next = min_target(next, key.visible_at_ms());
94                break;
95            }
96            let deadline = row.lease_deadline_ms();
97            if deadline > now_ms {
98                next = min_target(next, deadline);
99                continue;
100            }
101            return Ok(Self {
102                due: true,
103                delay: Some(Duration::ZERO),
104            });
105        }
106        Ok(Self {
107            due: false,
108            delay: next.map(|target| {
109                Duration::from_millis(target.saturating_sub(now_ms)).min(wakeup_recheck)
110            }),
111        })
112    }
113}
114
115fn min_target(current: Option<u64>, candidate: u64) -> Option<u64> {
116    Some(current.map_or(candidate, |current| current.min(candidate)))
117}
118
119fn now_ms() -> DbResult<u64> {
120    let millis = SystemTime::now()
121        .duration_since(UNIX_EPOCH)
122        .map_err(|_| DbError::Client("queue time is before unix epoch"))?
123        .as_millis();
124    u64::try_from(millis).map_err(|_| DbError::Client("queue time exceeds u64 milliseconds"))
125}
126
127#[doc(hidden)]
128pub struct QueueWait {
129    state: Mutex<WaitState>,
130    changed: Condvar,
131    pub(crate) service: Arc<QueueWakeService>,
132    recheck: Duration,
133}
134
135struct WaitState {
136    generation: u64,
137    blockers: HashMap<u64, u64>,
138    next_blocker: u64,
139    registrations: usize,
140}
141
142enum RegisterResult {
143    Registered(u64),
144    Changed,
145    Shutdown,
146}
147
148impl QueueWait {
149    pub(crate) fn new(service: Arc<QueueWakeService>, recheck: Duration) -> Arc<Self> {
150        let wait = Arc::new(Self {
151            state: Mutex::new(WaitState {
152                generation: 0,
153                blockers: HashMap::new(),
154                next_blocker: 1,
155                registrations: 0,
156            }),
157            changed: Condvar::new(),
158            service,
159            recheck,
160        });
161        // `shutdown_wake` only reaches async registrations, which live on the
162        // service. A thread parked in `wait_due_timeout` waits on *this*
163        // queue's condvar, so it needs its own listener or it sleeps out its
164        // full timeout on a database that is being torn down.
165        let weak = Arc::downgrade(&wait);
166        wait.service.shutdown.listen(Arc::new(move || {
167            if let Some(wait) = weak.upgrade() {
168                wait.notify_all_blockers();
169            }
170        }));
171        wait
172    }
173
174    /// Publish a visibility change before waking this queue's async waiters.
175    /// The queue mutex is never held while taking the service mutex, so worker
176    /// expiry (service-only) and direct producers cannot form a lock cycle.
177    pub(crate) fn notify_visible_at(&self, visible_at_ms: u64) {
178        {
179            let mut state = lock(&self.state);
180            state.generation = state.generation.wrapping_add(1);
181            if visible_at_ms < blocker_threshold(&state) {
182                self.changed.notify_all();
183            }
184        }
185        self.service.wake_queue(self, visible_at_ms);
186    }
187
188    /// Wake every blocking waiter, whatever it announced.
189    ///
190    /// Used for events that are not a visibility change — today, shutdown.
191    fn notify_all_blockers(&self) {
192        let mut state = lock(&self.state);
193        state.generation = state.generation.wrapping_add(1);
194        self.changed.notify_all();
195    }
196
197    fn generation(&self) -> u64 {
198        lock(&self.state).generation
199    }
200
201    fn wait_for_change(&self, delay: Duration, announced_until_ms: u64, generation: u64) -> bool {
202        let mut state = lock(&self.state);
203        if state.generation != generation {
204            return false;
205        }
206        let id = state.next_blocker;
207        state.next_blocker = state.next_blocker.wrapping_add(1);
208        state.blockers.insert(id, announced_until_ms);
209        let mut state = cv_wait_timeout(&self.changed, &self.state, state, delay);
210        state.blockers.remove(&id);
211        true
212    }
213
214    fn register(
215        &self,
216        id: Option<u64>,
217        waker: Waker,
218        delay: Duration,
219        generation: u64,
220    ) -> RegisterResult {
221        let mut state = lock(&self.state);
222        if state.generation != generation {
223            return RegisterResult::Changed;
224        }
225        if self.service.shutdown.is_shutdown() {
226            return RegisterResult::Shutdown;
227        }
228        let is_new = id.is_none();
229        let announced_until_ms = now_ms()
230            .unwrap_or(u64::MAX)
231            .saturating_add(u64::try_from(delay.as_millis()).unwrap_or(u64::MAX));
232        let Some(id) = self
233            .service
234            .register_locked(id, waker, delay, announced_until_ms, self)
235        else {
236            return RegisterResult::Shutdown;
237        };
238        if is_new {
239            state.registrations += 1;
240        }
241        RegisterResult::Registered(id)
242    }
243
244    fn deregistered(&self) {
245        let mut state = lock(&self.state);
246        state.registrations = state.registrations.saturating_sub(1);
247    }
248
249    #[cfg(all(debug_assertions, feature = "queue-test-support"))]
250    pub(crate) fn registration_count(&self) -> usize {
251        lock(&self.state).registrations
252    }
253}
254
255/// The visibility below which a `notify_all` is worth issuing.
256///
257/// `notify_visible_at` wakes blocking waiters all at once, so the aggregate has
258/// to be the *latest* announced deadline, not the earliest: taking the minimum
259/// would let one waiter with a short deadline suppress the notification for
260/// every waiter with a longer one, which then sleeps out its whole recheck
261/// instead of re-deriving a nearer delay. Waking a waiter early is free — it
262/// rescans and parks again.
263fn blocker_threshold(state: &WaitState) -> u64 {
264    state.blockers.values().copied().max().unwrap_or(u64::MAX)
265}
266
267pub(crate) struct QueueWakeService {
268    state: Mutex<WakeState>,
269    changed: Condvar,
270    pub(crate) shutdown: ShutdownSignal,
271    worker: Mutex<Option<JoinHandle<()>>>,
272    next_id: AtomicU64,
273}
274
275struct WakeState {
276    registrations: HashMap<u64, Registration>,
277}
278
279struct Registration {
280    queue: usize,
281    announced_until_ms: u64,
282    deadline: Instant,
283    waker: Waker,
284}
285
286impl QueueWakeService {
287    pub(crate) fn new(shutdown: ShutdownSignal) -> Arc<Self> {
288        let service = Arc::new(Self {
289            state: Mutex::new(WakeState {
290                registrations: HashMap::new(),
291            }),
292            changed: Condvar::new(),
293            shutdown: shutdown.clone(),
294            worker: Mutex::new(None),
295            next_id: AtomicU64::new(1),
296        });
297        let weak = Arc::downgrade(&service);
298        shutdown.listen(Arc::new(move || {
299            if let Some(service) = weak.upgrade() {
300                service.shutdown_wake();
301            }
302        }));
303        service
304    }
305
306    fn register_locked(
307        &self,
308        id: Option<u64>,
309        waker: Waker,
310        delay: Duration,
311        announced_until_ms: u64,
312        queue: &QueueWait,
313    ) -> Option<u64> {
314        let id = id.unwrap_or_else(|| self.next_id.fetch_add(1, Ordering::Relaxed));
315        let mut state = lock(&self.state);
316        if self.shutdown.is_shutdown() {
317            return None;
318        }
319        state.registrations.insert(
320            id,
321            Registration {
322                queue: queue as *const QueueWait as usize,
323                announced_until_ms,
324                deadline: Instant::now() + delay,
325                waker,
326            },
327        );
328        self.changed.notify_all();
329        Some(id)
330    }
331
332    fn deregister(&self, id: u64) {
333        lock(&self.state).registrations.remove(&id);
334        self.changed.notify_all();
335    }
336
337    fn wake_queue(&self, queue: &QueueWait, visible_at_ms: u64) {
338        let queue = queue as *const QueueWait as usize;
339        let wakers = {
340            let mut state = lock(&self.state);
341            let ids: Vec<_> = state
342                .registrations
343                .iter()
344                .filter_map(|(id, registration)| {
345                    (registration.queue == queue && visible_at_ms < registration.announced_until_ms)
346                        .then_some(*id)
347                })
348                .collect();
349            ids.into_iter()
350                .filter_map(|id| {
351                    state
352                        .registrations
353                        .remove(&id)
354                        .map(|registration| registration.waker)
355                })
356                .collect::<Vec<_>>()
357        };
358        for waker in wakers {
359            waker.wake();
360        }
361    }
362
363    fn shutdown_wake(&self) {
364        let wakers = {
365            let mut state = lock(&self.state);
366            state
367                .registrations
368                .drain()
369                .map(|(_, registration)| registration.waker)
370                .collect::<Vec<_>>()
371        };
372        self.changed.notify_all();
373        for waker in wakers {
374            waker.wake();
375        }
376    }
377
378    fn start(self: &Arc<Self>) {
379        let mut worker = lock(&self.worker);
380        if worker.is_some() {
381            return;
382        }
383        let service = Arc::clone(self);
384        *worker = Some(std::thread::spawn(move || service.run()));
385    }
386
387    fn run(self: Arc<Self>) {
388        loop {
389            if self.shutdown.is_shutdown() {
390                self.shutdown_wake();
391                return;
392            }
393            #[cfg(all(debug_assertions, feature = "queue-test-support"))]
394            test_support::wait_before_park();
395            let wakers = {
396                let mut state = lock(&self.state);
397                let now = Instant::now();
398                let ids: Vec<_> = state
399                    .registrations
400                    .iter()
401                    .filter_map(|(id, registration)| (registration.deadline <= now).then_some(*id))
402                    .collect();
403                if !ids.is_empty() {
404                    ids.into_iter()
405                        .filter_map(|id| {
406                            state
407                                .registrations
408                                .remove(&id)
409                                .map(|registration| registration.waker)
410                        })
411                        .collect()
412                } else {
413                    let delay = state
414                        .registrations
415                        .values()
416                        .map(|registration| registration.deadline)
417                        .min()
418                        .map_or(WORKER_IDLE_PARK, |deadline| {
419                            deadline.saturating_duration_since(now)
420                        });
421                    // The shutdown re-check must happen under `state`, not only
422                    // at the top of the loop: `shutdown_wake` notifies after
423                    // releasing this same mutex, so a worker preempted between
424                    // the top-of-loop check and this park would miss the
425                    // notification entirely and sleep out `WORKER_IDLE_PARK`
426                    // while `Db::drop` blocks in `join`.
427                    if self.shutdown.is_shutdown() {
428                        drop(state);
429                        self.shutdown_wake();
430                        return;
431                    }
432                    drop(cv_wait_timeout(&self.changed, &self.state, state, delay));
433                    Vec::new()
434                }
435            };
436            for waker in wakers {
437                waker.wake();
438            }
439        }
440    }
441
442    pub(crate) fn join(&self) {
443        self.shutdown_wake();
444        if let Some(worker) = lock(&self.worker).take() {
445            let _ = worker.join();
446        }
447    }
448}
449
450/// Runtime-agnostic future that resolves once queue work can be leased.
451pub struct QueueDue<'a, N: QueueSpec>
452where
453    N::Mode: QueueHandle<N>,
454{
455    queue: &'a Queue<N>,
456    registration: Option<u64>,
457}
458
459impl<'a, N> QueueDue<'a, N>
460where
461    N: QueueSpec,
462    N::Mode: QueueHandle<N>,
463{
464    pub(crate) fn new(queue: &'a Queue<N>) -> Self {
465        Self {
466            queue,
467            registration: None,
468        }
469    }
470
471    fn unregister(&mut self) {
472        if let Some(id) = self.registration.take() {
473            let wait = <N::Mode as QueueHandle<N>>::wait(self.queue);
474            wait.service.deregister(id);
475            wait.deregistered();
476        }
477    }
478}
479
480impl<N> Future for QueueDue<'_, N>
481where
482    N: QueueSpec,
483    N::Mode: QueueHandle<N>,
484{
485    type Output = DbResult<()>;
486
487    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
488        loop {
489            let wait = <N::Mode as QueueHandle<N>>::wait(self.queue);
490            let generation = wait.generation();
491            let scan = match <N::Mode as QueueHandle<N>>::scan_due(self.queue) {
492                Ok(scan) => scan,
493                Err(err) => {
494                    self.unregister();
495                    return Poll::Ready(Err(err));
496                }
497            };
498            if wait.service.shutdown.is_shutdown() {
499                self.unregister();
500                return Poll::Ready(Err(DbError::Client("queue wait service is shut down")));
501            }
502            if scan.due {
503                self.unregister();
504                return Poll::Ready(Ok(()));
505            }
506            match wait.register(
507                self.registration,
508                cx.waker().clone(),
509                scan.delay.unwrap_or(wait.recheck),
510                generation,
511            ) {
512                RegisterResult::Registered(id) => {
513                    self.registration = Some(id);
514                    wait.service.start();
515                    return Poll::Pending;
516                }
517                RegisterResult::Changed => continue,
518                RegisterResult::Shutdown => {
519                    self.unregister();
520                    return Poll::Ready(Err(DbError::Client("queue wait service is shut down")));
521                }
522            }
523        }
524    }
525}
526
527impl<N> Drop for QueueDue<'_, N>
528where
529    N: QueueSpec,
530    N::Mode: QueueHandle<N>,
531{
532    fn drop(&mut self) {
533        self.unregister();
534    }
535}
536
537pub(crate) fn wait_due_timeout<N>(queue: &Queue<N>, timeout: Duration) -> DbResult<bool>
538where
539    N: QueueSpec,
540    N::Mode: QueueHandle<N>,
541{
542    let deadline = Instant::now() + timeout;
543    loop {
544        let wait = <N::Mode as QueueHandle<N>>::wait(queue);
545        let generation = wait.generation();
546        let scan = <N::Mode as QueueHandle<N>>::scan_due(queue)?;
547        if scan.due {
548            return Ok(true);
549        }
550        // Reported the same way the async twin reports it, so a
551        // `while !stop { q.wait_due_timeout(..) }` loop terminates instead of
552        // sleeping out its timeout against a shutting-down database.
553        if wait.service.shutdown.is_shutdown() {
554            return Err(DbError::Client("queue wait service is shut down"));
555        }
556        let remaining = deadline.saturating_duration_since(Instant::now());
557        if remaining.is_zero() {
558            return Ok(false);
559        }
560        let delay = scan
561            .delay
562            .unwrap_or(wait.recheck)
563            .min(wait.recheck)
564            .min(remaining);
565        let announced_until_ms =
566            now_ms()?.saturating_add(u64::try_from(delay.as_millis()).unwrap_or(u64::MAX));
567        let _ = wait.wait_for_change(delay, announced_until_ms, generation);
568    }
569}
570
571#[cfg(test)]
572mod tests {
573    use super::*;
574
575    #[derive(Clone, Copy)]
576    struct Header(u64);
577
578    impl QueueHeaderRow for Header {
579        fn lease_deadline_ms(&self) -> u64 {
580            self.0
581        }
582    }
583
584    #[test]
585    fn backward_clock_jump_is_capped_by_recheck_from_a_fresh_scan() {
586        let rows = [(QueueKey::live(110, 1), Header(0))];
587        let recheck = Duration::from_millis(5);
588        let before_jump = QueueScan::rows_at(rows, recheck, 100).unwrap();
589        let after_jump = QueueScan::rows_at(rows, recheck, 90).unwrap();
590
591        assert_eq!(before_jump.delay, Some(Duration::from_millis(5)));
592        assert_eq!(after_jump.delay, Some(Duration::from_millis(5)));
593    }
594}