summaryrefslogtreecommitdiffstats
path: root/third_party/rust/interrupt-support
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/interrupt-support')
-rw-r--r--third_party/rust/interrupt-support/.cargo-checksum.json1
-rw-r--r--third_party/rust/interrupt-support/Cargo.toml31
-rw-r--r--third_party/rust/interrupt-support/README.md4
-rw-r--r--third_party/rust/interrupt-support/src/error.rs15
-rw-r--r--third_party/rust/interrupt-support/src/interruptee.rs29
-rw-r--r--third_party/rust/interrupt-support/src/lib.rs16
-rw-r--r--third_party/rust/interrupt-support/src/shutdown.rs81
-rw-r--r--third_party/rust/interrupt-support/src/sql.rs122
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()
+ }
+}