interrupt_support/
shutdown.rs

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
/* This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/// Shutdown handling for database operations
///
/// This module allows us to enter shutdown mode, causing all `SqlInterruptScope` instances that opt-in to
/// to be permanently interrupted.  This means:
///
///   - All current scopes will be interrupted
///   - Any attempt to create a new scope will be interrupted
///
/// Here's how add shutdown support to a component:
///
///   - Database connections need to be wrapped in a type that:
///      - Implements `AsRef<SqlInterruptHandle>`.
///      - Gets wrapped in an `Arc<>`.  This is needed so the shutdown code can get a weak reference to
///        the instance.
///      - Calls `register_interrupt()` on creation
///   - Use `SqlInterruptScope::begin_interrupt_scope()` before each operation.
///     This will return an error if shutdown mode is in effect.
///     The interrupt scope should be periodically checked to handle the operation being interrupted/shutdown after it started.
///
///  See `PlacesDb::begin_interrupt_scope()` and `PlacesApi::new_connection()` for an example of
///  how this works.
use crate::Interruptee;
use parking_lot::Mutex;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Weak;

use crate::SqlInterruptHandle;

// Bool that tracks if we're in shutdown mode or not.  We use Ordering::Relaxed to read/write to
// variable.  It's just a flag so we don't need stronger synchronization guarantees.
static IN_SHUTDOWN: AtomicBool = AtomicBool::new(false);

// `SqlInterruptHandle` instances to interrupt when we shutdown
lazy_static::lazy_static! {
   static ref REGISTERED_INTERRUPTS: Mutex<Vec<Weak<dyn AsRef<SqlInterruptHandle> + Send + Sync>>> = Mutex::new(Vec::new());
}

/// Initiate shutdown mode
pub fn shutdown() {
    IN_SHUTDOWN.store(true, Ordering::Relaxed);
    for weak in REGISTERED_INTERRUPTS.lock().iter() {
        if let Some(interrupt) = weak.upgrade() {
            interrupt.as_ref().as_ref().interrupt()
        }
    }
}

/// Check if we're currently in shutdown mode
pub fn in_shutdown() -> bool {
    IN_SHUTDOWN.load(Ordering::Relaxed)
}

/// Register a ShutdownInterrupt implementation
///
/// Call this function to ensure that the `SqlInterruptHandle::interrupt()` method will be called
/// at shutdown.
pub fn register_interrupt(interrupt: Weak<dyn AsRef<SqlInterruptHandle> + Send + Sync>) {
    // Try to find an existing entry that's been dropped to replace.  This keeps the vector growth
    // in check
    let mut interrupts = REGISTERED_INTERRUPTS.lock();
    for weak in interrupts.iter_mut() {
        if weak.strong_count() == 0 {
            *weak = interrupt;
            return;
        }
    }
    // No empty slots, push the new value
    interrupts.push(interrupt);
}

// Implements Interruptee by checking if we've entered shutdown mode
pub struct ShutdownInterruptee;
impl Interruptee for ShutdownInterruptee {
    #[inline]
    fn was_interrupted(&self) -> bool {
        in_shutdown()
    }
}