diff options
Diffstat (limited to 'third_party/rust/interrupt-support')
-rw-r--r-- | third_party/rust/interrupt-support/.cargo-checksum.json | 1 | ||||
-rw-r--r-- | third_party/rust/interrupt-support/Cargo.toml | 31 | ||||
-rw-r--r-- | third_party/rust/interrupt-support/README.md | 4 | ||||
-rw-r--r-- | third_party/rust/interrupt-support/src/error.rs | 15 | ||||
-rw-r--r-- | third_party/rust/interrupt-support/src/interruptee.rs | 29 | ||||
-rw-r--r-- | third_party/rust/interrupt-support/src/lib.rs | 16 | ||||
-rw-r--r-- | third_party/rust/interrupt-support/src/shutdown.rs | 81 | ||||
-rw-r--r-- | third_party/rust/interrupt-support/src/sql.rs | 122 |
8 files changed, 299 insertions, 0 deletions
diff --git a/third_party/rust/interrupt-support/.cargo-checksum.json b/third_party/rust/interrupt-support/.cargo-checksum.json new file mode 100644 index 0000000000..a106d68b82 --- /dev/null +++ b/third_party/rust/interrupt-support/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"0d4ed8e02406a7c9a4f7219b5da2de5afc9357264c5d8b66c57d25c45be714ed","README.md":"7f1418b4a7c138ba20bcaea077fe6cf0d6ffbaf6df6b90c80efc52aa0d0e2e9f","src/error.rs":"b83cbe8abd22a9d687508d236a2a77e28b3fc6c39673633e5820cc0e3fc86cba","src/interruptee.rs":"c56f9ac610d0b24a128a907266432287558c4b73f6c24b82674ca7894181d18f","src/lib.rs":"cf44a84310913be5264e1c4a3e004a9f7a6cd82d01a109bb6ac4d6002b5dd560","src/shutdown.rs":"b9dfdb717932301035001a0398967384f1b993e14505860415d023febbe82d63","src/sql.rs":"7e050313884a281e6b3fc7a4514374e08cb9e5f3c5aefb873be92e56f30af660"},"package":null}
\ No newline at end of file diff --git a/third_party/rust/interrupt-support/Cargo.toml b/third_party/rust/interrupt-support/Cargo.toml new file mode 100644 index 0000000000..8a19048949 --- /dev/null +++ b/third_party/rust/interrupt-support/Cargo.toml @@ -0,0 +1,31 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +name = "interrupt-support" +version = "0.1.0" +authors = ["application-services@mozilla.com"] +readme = "README.md" +license = "MPL-2.0" + +[dependencies] +lazy_static = "1.4" +parking_lot = ">=0.11,<=0.12" + +[dependencies.rusqlite] +version = "0.28.0" +features = [ + "functions", + "limits", + "bundled", + "unlock_notify", +] diff --git a/third_party/rust/interrupt-support/README.md b/third_party/rust/interrupt-support/README.md new file mode 100644 index 0000000000..5a9fbe5f39 --- /dev/null +++ b/third_party/rust/interrupt-support/README.md @@ -0,0 +1,4 @@ +## Interrupt crate + +This create exposes traits and errors to allow for interrupt support across +the various crates in this repository. diff --git a/third_party/rust/interrupt-support/src/error.rs b/third_party/rust/interrupt-support/src/error.rs new file mode 100644 index 0000000000..6718a16480 --- /dev/null +++ b/third_party/rust/interrupt-support/src/error.rs @@ -0,0 +1,15 @@ +/* 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/. */ + +/// The error returned by err_if_interrupted. +#[derive(Debug, Clone)] +pub struct Interrupted; + +impl std::fmt::Display for Interrupted { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("The operation was interrupted") + } +} + +impl std::error::Error for Interrupted {} diff --git a/third_party/rust/interrupt-support/src/interruptee.rs b/third_party/rust/interrupt-support/src/interruptee.rs new file mode 100644 index 0000000000..3ee6eadfe6 --- /dev/null +++ b/third_party/rust/interrupt-support/src/interruptee.rs @@ -0,0 +1,29 @@ +/* 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/. */ + +use crate::Interrupted; + +/// Represents the state of something that may be interrupted. Decoupled from +/// the interrupt mechanics so that things which want to check if they have been +/// interrupted are simpler. +pub trait Interruptee { + fn was_interrupted(&self) -> bool; + + fn err_if_interrupted(&self) -> Result<(), Interrupted> { + if self.was_interrupted() { + return Err(Interrupted); + } + Ok(()) + } +} + +/// A convenience implementation, should only be used in tests. +pub struct NeverInterrupts; + +impl Interruptee for NeverInterrupts { + #[inline] + fn was_interrupted(&self) -> bool { + false + } +} diff --git a/third_party/rust/interrupt-support/src/lib.rs b/third_party/rust/interrupt-support/src/lib.rs new file mode 100644 index 0000000000..6d2ef8f8e5 --- /dev/null +++ b/third_party/rust/interrupt-support/src/lib.rs @@ -0,0 +1,16 @@ +/* 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/. */ + +#![allow(unknown_lints)] +#![warn(rust_2018_idioms)] + +mod error; +mod interruptee; +mod shutdown; +mod sql; + +pub use error::Interrupted; +pub use interruptee::*; +pub use shutdown::*; +pub use sql::*; diff --git a/third_party/rust/interrupt-support/src/shutdown.rs b/third_party/rust/interrupt-support/src/shutdown.rs new file mode 100644 index 0000000000..9c64df27e8 --- /dev/null +++ b/third_party/rust/interrupt-support/src/shutdown.rs @@ -0,0 +1,81 @@ +/* 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: +/// +/// - Use `SqlInterruptScope::new_with_shutdown_check()` to create a new +/// `SqlInterruptScope` +/// - 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 +/// +/// 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 guarentees. +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() + } +} diff --git a/third_party/rust/interrupt-support/src/sql.rs b/third_party/rust/interrupt-support/src/sql.rs new file mode 100644 index 0000000000..6f361013fc --- /dev/null +++ b/third_party/rust/interrupt-support/src/sql.rs @@ -0,0 +1,122 @@ +/* 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/. */ + +use crate::{in_shutdown, Interrupted, Interruptee}; +use rusqlite::{Connection, InterruptHandle}; +use std::fmt; +use std::sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, +}; + +/// Interrupt operations that use SQL +/// +/// Typical usage of this type: +/// - Components typically create a wrapper class around an `rusqlite::Connection` +/// (`PlacesConnection`, `LoginStore`, etc.) +/// - The wrapper stores an `Arc<SqlInterruptHandle>` +/// - The wrapper has a method that clones and returns that `Arc`. This allows passing the interrupt +/// handle to a different thread in order to interrupt a particular operation. +/// - The wrapper calls `begin_interrupt_scope()` at the start of each operation. The code that +/// performs the operation periodically calls `err_if_interrupted()`. +/// - Finally, the wrapper class implements `AsRef<SqlInterruptHandle>` and calls +/// `register_interrupt()`. This causes all operations to be interrupted when we enter +/// shutdown mode. +pub struct SqlInterruptHandle { + db_handle: InterruptHandle, + // Counter that we increment on each interrupt() call. + // We use Ordering::Relaxed to read/write to this variable. This is safe because we're + // basically using it as a flag and don't need stronger synchronization guarentees. + interrupt_counter: Arc<AtomicUsize>, +} + +impl SqlInterruptHandle { + #[inline] + pub fn new(conn: &Connection) -> Self { + Self { + db_handle: conn.get_interrupt_handle(), + interrupt_counter: Arc::new(AtomicUsize::new(0)), + } + } + + /// Begin an interrupt scope that will be interrupted by this handle + /// + /// Returns Err(Interrupted) if we're in shutdown mode + #[inline] + pub fn begin_interrupt_scope(&self) -> Result<SqlInterruptScope, Interrupted> { + if in_shutdown() { + Err(Interrupted) + } else { + Ok(SqlInterruptScope::new(Arc::clone(&self.interrupt_counter))) + } + } + + /// Interrupt all interrupt scopes created by this handle + #[inline] + pub fn interrupt(&self) { + self.interrupt_counter.fetch_add(1, Ordering::Relaxed); + self.db_handle.interrupt(); + } +} + +impl fmt::Debug for SqlInterruptHandle { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("SqlInterruptHandle") + .field( + "interrupt_counter", + &self.interrupt_counter.load(Ordering::Relaxed), + ) + .finish() + } +} + +/// Check if an operation has been interrupted +/// +/// This is used by the rust code to check if an operation should fail because it was interrupted. +/// It handles the case where we get interrupted outside of an SQL query. +#[derive(Debug)] +pub struct SqlInterruptScope { + start_value: usize, + interrupt_counter: Arc<AtomicUsize>, +} + +impl SqlInterruptScope { + fn new(interrupt_counter: Arc<AtomicUsize>) -> Self { + let start_value = interrupt_counter.load(Ordering::Relaxed); + Self { + start_value, + interrupt_counter, + } + } + + // Create an `SqlInterruptScope` that's never interrupted. + // + // This should only be used for testing purposes. + pub fn dummy() -> Self { + Self::new(Arc::new(AtomicUsize::new(0))) + } + + /// Check if scope has been interrupted + #[inline] + pub fn was_interrupted(&self) -> bool { + self.interrupt_counter.load(Ordering::Relaxed) != self.start_value + } + + /// Return Err(Interrupted) if we were interrupted + #[inline] + pub fn err_if_interrupted(&self) -> Result<(), Interrupted> { + if self.was_interrupted() { + Err(Interrupted) + } else { + Ok(()) + } + } +} + +impl Interruptee for SqlInterruptScope { + #[inline] + fn was_interrupted(&self) -> bool { + self.was_interrupted() + } +} |