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()
}
}