interrupt_support/
shutdown.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2License, v. 2.0. If a copy of the MPL was not distributed with this
3* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5/// Shutdown handling for database operations
6///
7/// This module allows us to enter shutdown mode, causing all `SqlInterruptScope` instances that opt-in to
8/// to be permanently interrupted.  This means:
9///
10///   - All current scopes will be interrupted
11///   - Any attempt to create a new scope will be interrupted
12///
13/// Here's how add shutdown support to a component:
14///
15///   - Database connections need to be wrapped in a type that:
16///      - Implements `AsRef<SqlInterruptHandle>`.
17///      - Gets wrapped in an `Arc<>`.  This is needed so the shutdown code can get a weak reference to
18///        the instance.
19///      - Calls `register_interrupt()` on creation
20///   - Use `SqlInterruptScope::begin_interrupt_scope()` before each operation.
21///     This will return an error if shutdown mode is in effect.
22///     The interrupt scope should be periodically checked to handle the operation being interrupted/shutdown after it started.
23///
24///  See `PlacesDb::begin_interrupt_scope()` and `PlacesApi::new_connection()` for an example of
25///  how this works.
26use crate::Interruptee;
27use parking_lot::Mutex;
28use std::sync::atomic::{AtomicBool, Ordering};
29use std::sync::Weak;
30
31use crate::SqlInterruptHandle;
32
33// Bool that tracks if we're in shutdown mode or not.  We use Ordering::Relaxed to read/write to
34// variable.  It's just a flag so we don't need stronger synchronization guarantees.
35static IN_SHUTDOWN: AtomicBool = AtomicBool::new(false);
36
37// `SqlInterruptHandle` instances to interrupt when we shutdown
38lazy_static::lazy_static! {
39   static ref REGISTERED_INTERRUPTS: Mutex<Vec<Weak<dyn AsRef<SqlInterruptHandle> + Send + Sync>>> = Mutex::new(Vec::new());
40}
41
42/// Initiate shutdown mode
43pub fn shutdown() {
44    IN_SHUTDOWN.store(true, Ordering::Relaxed);
45    for weak in REGISTERED_INTERRUPTS.lock().iter() {
46        if let Some(interrupt) = weak.upgrade() {
47            interrupt.as_ref().as_ref().interrupt()
48        }
49    }
50}
51
52/// Check if we're currently in shutdown mode
53pub fn in_shutdown() -> bool {
54    IN_SHUTDOWN.load(Ordering::Relaxed)
55}
56
57/// Register a ShutdownInterrupt implementation
58///
59/// Call this function to ensure that the `SqlInterruptHandle::interrupt()` method will be called
60/// at shutdown.
61pub fn register_interrupt(interrupt: Weak<dyn AsRef<SqlInterruptHandle> + Send + Sync>) {
62    // Try to find an existing entry that's been dropped to replace.  This keeps the vector growth
63    // in check
64    let mut interrupts = REGISTERED_INTERRUPTS.lock();
65    for weak in interrupts.iter_mut() {
66        if weak.strong_count() == 0 {
67            *weak = interrupt;
68            return;
69        }
70    }
71    // No empty slots, push the new value
72    interrupts.push(interrupt);
73}
74
75// Implements Interruptee by checking if we've entered shutdown mode
76pub struct ShutdownInterruptee;
77impl Interruptee for ShutdownInterruptee {
78    #[inline]
79    fn was_interrupted(&self) -> bool {
80        in_shutdown()
81    }
82}