Adding shutdown support to sql-support
When the user wants to shutdown the application, we should: - Interrupt all current `SqlInterruptScope`s - Interrupt all future `SqlInterruptScope`s when they're created. The nice thing about this approach is that it didn't require invasive changes in places to support it. The main new requirement was we need to have a way to get a `Weak<AsRef<SqlInterruptHandler>>` for each database. In order to support that, I needed to: - For the read/write and read-only connections: have `PlacesConnection` store an `SqlInterruptHandler` and implement `AsRef`. - For the sync connection: Added struct that wraps the `Mutex<PlacesDb>` and also stores a `SqlInterruptHandler` and implements `AsRef`. Updated `places-utils` so that ctrl-c starts shutdown mode.
This commit is contained in:
Родитель
d5c490946e
Коммит
91d0bab23e
|
@ -893,6 +893,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"sql-support",
|
||||||
"structopt",
|
"structopt",
|
||||||
"sync-guid",
|
"sync-guid",
|
||||||
"sync15",
|
"sync15",
|
||||||
|
@ -3315,6 +3316,7 @@ dependencies = [
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"log",
|
"log",
|
||||||
"nss_build_common",
|
"nss_build_common",
|
||||||
|
"parking_lot 0.5.5",
|
||||||
"rusqlite",
|
"rusqlite",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
|
|
@ -86,7 +86,7 @@ pub fn search_frecent(conn: &PlacesDb, params: SearchParams) -> Result<Vec<Searc
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn match_url(conn: &PlacesDb, query: impl AsRef<str>) -> Result<Option<Url>> {
|
pub fn match_url(conn: &PlacesDb, query: impl AsRef<str>) -> Result<Option<Url>> {
|
||||||
let scope = conn.begin_interrupt_scope();
|
let scope = conn.begin_interrupt_scope()?;
|
||||||
let matcher = OriginOrUrl::new(query.as_ref());
|
let matcher = OriginOrUrl::new(query.as_ref());
|
||||||
// Note: The matcher ignores the limit argument (it's a trait method)
|
// Note: The matcher ignores the limit argument (it's a trait method)
|
||||||
let results = matcher.search(conn, 1)?;
|
let results = matcher.search(conn, 1)?;
|
||||||
|
@ -107,7 +107,7 @@ fn match_with_limit(
|
||||||
) -> Result<Vec<SearchResult>> {
|
) -> Result<Vec<SearchResult>> {
|
||||||
let mut results = Vec::new();
|
let mut results = Vec::new();
|
||||||
let mut rem_results = max_results;
|
let mut rem_results = max_results;
|
||||||
let scope = conn.begin_interrupt_scope();
|
let scope = conn.begin_interrupt_scope()?;
|
||||||
for m in matchers {
|
for m in matchers {
|
||||||
if rem_results == 0 {
|
if rem_results == 0 {
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
use crate::bookmark_sync::BookmarksSyncEngine;
|
use crate::bookmark_sync::BookmarksSyncEngine;
|
||||||
use crate::db::db::PlacesDb;
|
use crate::db::db::{PlacesDb, SharedPlacesDb};
|
||||||
use crate::error::*;
|
use crate::error::*;
|
||||||
use crate::history_sync::HistorySyncEngine;
|
use crate::history_sync::HistorySyncEngine;
|
||||||
use crate::storage::{
|
use crate::storage::{
|
||||||
|
@ -13,7 +13,7 @@ use crate::util::normalize_path;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use rusqlite::OpenFlags;
|
use rusqlite::OpenFlags;
|
||||||
use sql_support::{SqlInterruptHandle, SqlInterruptScope};
|
use sql_support::{register_interrupt, SqlInterruptHandle};
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
@ -129,7 +129,7 @@ pub struct PlacesApi {
|
||||||
// called again, we reuse it.
|
// called again, we reuse it.
|
||||||
// - The outer mutex synchronizes the `get_sync_connection()` operation. If multiple threads
|
// - The outer mutex synchronizes the `get_sync_connection()` operation. If multiple threads
|
||||||
// ran that at the same time there would be issues.
|
// ran that at the same time there would be issues.
|
||||||
sync_connection: Mutex<Weak<Mutex<PlacesDb>>>,
|
sync_connection: Mutex<Weak<SharedPlacesDb>>,
|
||||||
id: usize,
|
id: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,7 +217,7 @@ impl PlacesApi {
|
||||||
// - Each connection is wrapped in a `Mutex<>` to synchronize access.
|
// - Each connection is wrapped in a `Mutex<>` to synchronize access.
|
||||||
// - The mutex is then wrapped in an Arc<>. If the last Arc<> returned is still alive, then
|
// - The mutex is then wrapped in an Arc<>. If the last Arc<> returned is still alive, then
|
||||||
// get_sync_connection() will reuse it.
|
// get_sync_connection() will reuse it.
|
||||||
pub fn get_sync_connection(&self) -> Result<Arc<Mutex<PlacesDb>>> {
|
pub fn get_sync_connection(&self) -> Result<Arc<SharedPlacesDb>> {
|
||||||
// First step: lock the outer mutex
|
// First step: lock the outer mutex
|
||||||
let mut conn = self.sync_connection.lock();
|
let mut conn = self.sync_connection.lock();
|
||||||
match conn.upgrade() {
|
match conn.upgrade() {
|
||||||
|
@ -225,12 +225,13 @@ impl PlacesApi {
|
||||||
Some(db) => Ok(db),
|
Some(db) => Ok(db),
|
||||||
// If not, create a new connection
|
// If not, create a new connection
|
||||||
None => {
|
None => {
|
||||||
let db = Arc::new(Mutex::new(PlacesDb::open(
|
let db = Arc::new(SharedPlacesDb::new(PlacesDb::open(
|
||||||
self.db_name.clone(),
|
self.db_name.clone(),
|
||||||
ConnectionType::Sync,
|
ConnectionType::Sync,
|
||||||
self.id,
|
self.id,
|
||||||
self.coop_tx_lock.clone(),
|
self.coop_tx_lock.clone(),
|
||||||
)?));
|
)?));
|
||||||
|
register_interrupt(Arc::<SharedPlacesDb>::downgrade(&db));
|
||||||
// Store a weakref for next time
|
// Store a weakref for next time
|
||||||
*conn = Arc::downgrade(&db);
|
*conn = Arc::downgrade(&db);
|
||||||
Ok(db)
|
Ok(db)
|
||||||
|
@ -330,7 +331,7 @@ impl PlacesApi {
|
||||||
syncer: F,
|
syncer: F,
|
||||||
) -> Result<telemetry::SyncTelemetryPing>
|
) -> Result<telemetry::SyncTelemetryPing>
|
||||||
where
|
where
|
||||||
F: FnOnce(Arc<Mutex<PlacesDb>>, &mut MemoryCachedState, &mut Option<String>) -> SyncResult,
|
F: FnOnce(Arc<SharedPlacesDb>, &mut MemoryCachedState, &mut Option<String>) -> SyncResult,
|
||||||
{
|
{
|
||||||
let mut guard = self.sync_state.lock();
|
let mut guard = self.sync_state.lock();
|
||||||
let conn = self.get_sync_connection()?;
|
let conn = self.get_sync_connection()?;
|
||||||
|
@ -450,15 +451,6 @@ impl PlacesApi {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new SqlInterruptScope for the syncing code
|
|
||||||
///
|
|
||||||
/// The syncing code expects a SqlInterruptScope, but it's never been actually hooked up to
|
|
||||||
/// anything. This method returns something to make the compiler happy, but we should replace
|
|
||||||
/// this with working code as part of #1684.
|
|
||||||
pub fn dummy_sync_interrupt_scope(&self) -> SqlInterruptScope {
|
|
||||||
SqlInterruptScope::new(Arc::new(AtomicUsize::new(0)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated/Broken interrupt handler method
|
// Deprecated/Broken interrupt handler method
|
||||||
// This should be removed as part of https://github.com/mozilla/application-services/issues/1684
|
// This should be removed as part of https://github.com/mozilla/application-services/issues/1684
|
||||||
//
|
//
|
||||||
|
|
|
@ -8,7 +8,7 @@ use super::record::{
|
||||||
SeparatorRecord,
|
SeparatorRecord,
|
||||||
};
|
};
|
||||||
use super::{SyncedBookmarkKind, SyncedBookmarkValidity};
|
use super::{SyncedBookmarkKind, SyncedBookmarkValidity};
|
||||||
use crate::db::{GlobalChangeCounterTracker, PlacesDb};
|
use crate::db::{GlobalChangeCounterTracker, PlacesDb, SharedPlacesDb};
|
||||||
use crate::error::*;
|
use crate::error::*;
|
||||||
use crate::frecency::{calculate_frecency, DEFAULT_FRECENCY_SETTINGS};
|
use crate::frecency::{calculate_frecency, DEFAULT_FRECENCY_SETTINGS};
|
||||||
use crate::storage::{
|
use crate::storage::{
|
||||||
|
@ -23,7 +23,6 @@ use dogear::{
|
||||||
self, AbortSignal, CompletionOps, Content, Item, MergedRoot, TelemetryEvent, Tree, UploadItem,
|
self, AbortSignal, CompletionOps, Content, Item, MergedRoot, TelemetryEvent, Tree, UploadItem,
|
||||||
UploadTombstone,
|
UploadTombstone,
|
||||||
};
|
};
|
||||||
use parking_lot::Mutex;
|
|
||||||
use rusqlite::{Row, NO_PARAMS};
|
use rusqlite::{Row, NO_PARAMS};
|
||||||
use sql_support::{self, ConnExt, SqlInterruptScope};
|
use sql_support::{self, ConnExt, SqlInterruptScope};
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
@ -887,14 +886,14 @@ pub(crate) fn update_frecencies(db: &PlacesDb, scope: &SqlInterruptScope) -> Res
|
||||||
|
|
||||||
// Short-lived struct that's constructed each sync
|
// Short-lived struct that's constructed each sync
|
||||||
pub struct BookmarksSyncEngine {
|
pub struct BookmarksSyncEngine {
|
||||||
db: Arc<Mutex<PlacesDb>>,
|
db: Arc<SharedPlacesDb>,
|
||||||
// Pub so that it can be used by the PlacesApi methods. Once all syncing goes through the
|
// Pub so that it can be used by the PlacesApi methods. Once all syncing goes through the
|
||||||
// `SyncManager` we should be able to make this private.
|
// `SyncManager` we should be able to make this private.
|
||||||
pub(crate) scope: SqlInterruptScope,
|
pub(crate) scope: SqlInterruptScope,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BookmarksSyncEngine {
|
impl BookmarksSyncEngine {
|
||||||
pub fn new(db: Arc<Mutex<PlacesDb>>) -> Self {
|
pub fn new(db: Arc<SharedPlacesDb>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
db,
|
db,
|
||||||
scope: SqlInterruptScope::new(Arc::new(AtomicUsize::new(0))),
|
scope: SqlInterruptScope::new(Arc::new(AtomicUsize::new(0))),
|
||||||
|
@ -1902,7 +1901,7 @@ mod tests {
|
||||||
let conn = db.lock();
|
let conn = db.lock();
|
||||||
|
|
||||||
// suck records into the database.
|
// suck records into the database.
|
||||||
let interrupt_scope = api.dummy_sync_interrupt_scope();
|
let interrupt_scope = conn.begin_interrupt_scope()?;
|
||||||
|
|
||||||
let mut incoming = IncomingChangeset::new(COLLECTION_NAME, ServerTimestamp(0));
|
let mut incoming = IncomingChangeset::new(COLLECTION_NAME, ServerTimestamp(0));
|
||||||
|
|
||||||
|
@ -1991,7 +1990,7 @@ mod tests {
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
let interrupt_scope = syncer.begin_interrupt_scope();
|
let interrupt_scope = syncer.begin_interrupt_scope()?;
|
||||||
let merger =
|
let merger =
|
||||||
Merger::with_localtime(&syncer, &interrupt_scope, ServerTimestamp(0), now.into());
|
Merger::with_localtime(&syncer, &interrupt_scope, ServerTimestamp(0), now.into());
|
||||||
|
|
||||||
|
@ -2126,7 +2125,7 @@ mod tests {
|
||||||
|
|
||||||
let sync_db = api.get_sync_connection().unwrap();
|
let sync_db = api.get_sync_connection().unwrap();
|
||||||
let syncer = sync_db.lock();
|
let syncer = sync_db.lock();
|
||||||
let interrupt_scope = api.dummy_sync_interrupt_scope();
|
let interrupt_scope = syncer.begin_interrupt_scope().unwrap();
|
||||||
|
|
||||||
update_frecencies(&syncer, &interrupt_scope).expect("Should update frecencies");
|
update_frecencies(&syncer, &interrupt_scope).expect("Should update frecencies");
|
||||||
|
|
||||||
|
@ -2707,7 +2706,7 @@ mod tests {
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let interrupt_scope = db.begin_interrupt_scope();
|
let interrupt_scope = db.begin_interrupt_scope()?;
|
||||||
|
|
||||||
let mut merger = Merger::new(&db, &interrupt_scope, ServerTimestamp(0));
|
let mut merger = Merger::new(&db, &interrupt_scope, ServerTimestamp(0));
|
||||||
merger.merge()?;
|
merger.merge()?;
|
||||||
|
|
|
@ -158,8 +158,10 @@ impl PlacesDb {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn begin_interrupt_scope(&self) -> SqlInterruptScope {
|
pub fn begin_interrupt_scope(&self) -> Result<SqlInterruptScope> {
|
||||||
SqlInterruptScope::new(self.interrupt_counter.clone())
|
Ok(SqlInterruptScope::new_with_shutdown_check(
|
||||||
|
self.interrupt_counter.clone(),
|
||||||
|
)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -207,6 +209,46 @@ impl Deref for PlacesDb {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// PlacesDB that's behind a Mutex so it can be shared between threads
|
||||||
|
pub struct SharedPlacesDb {
|
||||||
|
db: Mutex<PlacesDb>,
|
||||||
|
interrupt_handle: SqlInterruptHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SharedPlacesDb {
|
||||||
|
pub fn new(db: PlacesDb) -> Self {
|
||||||
|
Self {
|
||||||
|
interrupt_handle: db.new_interrupt_handle(),
|
||||||
|
db: Mutex::new(db),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Interrupt any current DB operation
|
||||||
|
///
|
||||||
|
/// This method doesn't need to take the Mutex lock (which would defeat the point of
|
||||||
|
/// interruption, since taking the lock would need to wait for the operation to complete).
|
||||||
|
pub fn interrupt(&self) {
|
||||||
|
self.interrupt_handle.interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deref to a Mutex<PlacesDb>, which is how we will use SharedPlacesDb most of the time
|
||||||
|
impl Deref for SharedPlacesDb {
|
||||||
|
type Target = Mutex<PlacesDb>;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn deref(&self) -> &Mutex<PlacesDb> {
|
||||||
|
&self.db
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also implement AsRef<SqlInterruptHandle> so that we can interrupt this at shutdown
|
||||||
|
impl AsRef<SqlInterruptHandle> for SharedPlacesDb {
|
||||||
|
fn as_ref(&self) -> &SqlInterruptHandle {
|
||||||
|
&self.interrupt_handle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// An object that can tell you whether a bookmark changing operation has
|
/// An object that can tell you whether a bookmark changing operation has
|
||||||
/// happened since the object was created.
|
/// happened since the object was created.
|
||||||
pub struct GlobalChangeCounterTracker {
|
pub struct GlobalChangeCounterTracker {
|
||||||
|
|
|
@ -9,4 +9,4 @@ mod schema;
|
||||||
mod tx;
|
mod tx;
|
||||||
pub use self::tx::PlacesTransaction;
|
pub use self::tx::PlacesTransaction;
|
||||||
|
|
||||||
pub use crate::db::db::{GlobalChangeCounterTracker, PlacesDb};
|
pub use crate::db::db::{GlobalChangeCounterTracker, PlacesDb, SharedPlacesDb};
|
||||||
|
|
|
@ -24,7 +24,7 @@ use crate::VisitObservation;
|
||||||
use crate::VisitTransition;
|
use crate::VisitTransition;
|
||||||
use crate::{PlacesApi, PlacesDb};
|
use crate::{PlacesApi, PlacesDb};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use sql_support::SqlInterruptHandle;
|
use sql_support::{register_interrupt, SqlInterruptHandle};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use sync_guid::Guid;
|
use sync_guid::Guid;
|
||||||
use types::Timestamp;
|
use types::Timestamp;
|
||||||
|
@ -106,8 +106,9 @@ impl UniffiCustomTypeWrapper for Guid {
|
||||||
impl PlacesApi {
|
impl PlacesApi {
|
||||||
fn new_connection(&self, conn_type: ConnectionType) -> Result<Arc<PlacesConnection>> {
|
fn new_connection(&self, conn_type: ConnectionType) -> Result<Arc<PlacesConnection>> {
|
||||||
let db = self.open_connection(conn_type)?;
|
let db = self.open_connection(conn_type)?;
|
||||||
let connection = PlacesConnection { db: Mutex::new(db) };
|
let connection = Arc::new(PlacesConnection::new(db));
|
||||||
Ok(Arc::new(connection))
|
register_interrupt(Arc::<PlacesConnection>::downgrade(&connection));
|
||||||
|
Ok(connection)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: These methods are unused on Android but will remain needed for
|
// NOTE: These methods are unused on Android but will remain needed for
|
||||||
|
@ -188,9 +189,17 @@ impl PlacesApi {
|
||||||
|
|
||||||
pub struct PlacesConnection {
|
pub struct PlacesConnection {
|
||||||
db: Mutex<PlacesDb>,
|
db: Mutex<PlacesDb>,
|
||||||
|
interrupt_handle: SqlInterruptHandle,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlacesConnection {
|
impl PlacesConnection {
|
||||||
|
pub fn new(db: PlacesDb) -> Self {
|
||||||
|
Self {
|
||||||
|
interrupt_handle: db.new_interrupt_handle(),
|
||||||
|
db: Mutex::new(db),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// A helper that gets the connection from the mutex and converts errors.
|
// A helper that gets the connection from the mutex and converts errors.
|
||||||
fn with_conn<F, T>(&self, f: F) -> Result<T>
|
fn with_conn<F, T>(&self, f: F) -> Result<T>
|
||||||
where
|
where
|
||||||
|
@ -513,6 +522,12 @@ impl PlacesConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AsRef<SqlInterruptHandle> for PlacesConnection {
|
||||||
|
fn as_ref(&self) -> &SqlInterruptHandle {
|
||||||
|
&self.interrupt_handle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub struct HistoryVisitInfo {
|
pub struct HistoryVisitInfo {
|
||||||
pub url: Url,
|
pub url: Url,
|
||||||
|
@ -587,20 +602,14 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_accept_result_with_invalid_url() {
|
fn test_accept_result_with_invalid_url() {
|
||||||
let api = new_mem_connection();
|
let conn = PlacesConnection::new(new_mem_connection());
|
||||||
let conn = PlacesConnection {
|
|
||||||
db: Mutex::new(api),
|
|
||||||
};
|
|
||||||
let invalid_url = "http://1234.56.78.90".to_string();
|
let invalid_url = "http://1234.56.78.90".to_string();
|
||||||
assert!(PlacesConnection::accept_result(&conn, "ample".to_string(), invalid_url).is_ok());
|
assert!(PlacesConnection::accept_result(&conn, "ample".to_string(), invalid_url).is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_bookmarks_get_all_with_url_with_invalid_url() {
|
fn test_bookmarks_get_all_with_url_with_invalid_url() {
|
||||||
let api = new_mem_connection();
|
let conn = PlacesConnection::new(new_mem_connection());
|
||||||
let conn = PlacesConnection {
|
|
||||||
db: Mutex::new(api),
|
|
||||||
};
|
|
||||||
let invalid_url = "http://1234.56.78.90".to_string();
|
let invalid_url = "http://1234.56.78.90".to_string();
|
||||||
assert!(PlacesConnection::bookmarks_get_all_with_url(&conn, invalid_url).is_ok());
|
assert!(PlacesConnection::bookmarks_get_all_with_url(&conn, invalid_url).is_ok());
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,10 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
use crate::db::PlacesDb;
|
use crate::db::{PlacesDb, SharedPlacesDb};
|
||||||
use crate::error::*;
|
use crate::error::*;
|
||||||
use crate::storage::history::{delete_everything, history_sync::reset};
|
use crate::storage::history::{delete_everything, history_sync::reset};
|
||||||
use crate::storage::{get_meta, put_meta};
|
use crate::storage::{get_meta, put_meta};
|
||||||
use parking_lot::Mutex;
|
|
||||||
use sql_support::SqlInterruptScope;
|
use sql_support::SqlInterruptScope;
|
||||||
use std::sync::{atomic::AtomicUsize, Arc};
|
use std::sync::{atomic::AtomicUsize, Arc};
|
||||||
use sync15::telemetry;
|
use sync15::telemetry;
|
||||||
|
@ -65,14 +64,14 @@ fn do_sync_finished(
|
||||||
|
|
||||||
// Short-lived struct that's constructed each sync
|
// Short-lived struct that's constructed each sync
|
||||||
pub struct HistorySyncEngine {
|
pub struct HistorySyncEngine {
|
||||||
pub db: Arc<Mutex<PlacesDb>>,
|
pub db: Arc<SharedPlacesDb>,
|
||||||
// Public because we use it in the [PlacesApi] sync methods. We can probably make this private
|
// Public because we use it in the [PlacesApi] sync methods. We can probably make this private
|
||||||
// once all syncing goes through the sync manager.
|
// once all syncing goes through the sync manager.
|
||||||
pub(crate) scope: SqlInterruptScope,
|
pub(crate) scope: SqlInterruptScope,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HistorySyncEngine {
|
impl HistorySyncEngine {
|
||||||
pub fn new(db: Arc<Mutex<PlacesDb>>) -> Self {
|
pub fn new(db: Arc<SharedPlacesDb>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
db,
|
db,
|
||||||
scope: SqlInterruptScope::new(Arc::new(AtomicUsize::new(0))),
|
scope: SqlInterruptScope::new(Arc::new(AtomicUsize::new(0))),
|
||||||
|
|
|
@ -58,7 +58,7 @@ fn do_import(places_api: &PlacesApi, fennec_db_file_url: Url) -> Result<Bookmark
|
||||||
let conn_mutex = places_api.get_sync_connection()?;
|
let conn_mutex = places_api.get_sync_connection()?;
|
||||||
let conn = conn_mutex.lock();
|
let conn = conn_mutex.lock();
|
||||||
|
|
||||||
let scope = conn.begin_interrupt_scope();
|
let scope = conn.begin_interrupt_scope()?;
|
||||||
|
|
||||||
sql_fns::define_functions(&conn)?;
|
sql_fns::define_functions(&conn)?;
|
||||||
|
|
||||||
|
@ -163,7 +163,7 @@ fn do_pinned_sites_import(
|
||||||
) -> Result<Vec<BookmarkData>> {
|
) -> Result<Vec<BookmarkData>> {
|
||||||
let conn_mutex = places_api.get_sync_connection()?;
|
let conn_mutex = places_api.get_sync_connection()?;
|
||||||
let conn = conn_mutex.lock();
|
let conn = conn_mutex.lock();
|
||||||
let scope = conn.begin_interrupt_scope();
|
let scope = conn.begin_interrupt_scope()?;
|
||||||
|
|
||||||
sql_fns::define_functions(&conn)?;
|
sql_fns::define_functions(&conn)?;
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,7 @@ fn do_import(places_api: &PlacesApi, android_db_file_url: Url) -> Result<History
|
||||||
let conn_mutex = places_api.get_sync_connection()?;
|
let conn_mutex = places_api.get_sync_connection()?;
|
||||||
let conn = conn_mutex.lock();
|
let conn = conn_mutex.lock();
|
||||||
|
|
||||||
let scope = conn.begin_interrupt_scope();
|
let scope = conn.begin_interrupt_scope()?;
|
||||||
|
|
||||||
define_sql_functions(&conn)?;
|
define_sql_functions(&conn)?;
|
||||||
|
|
||||||
|
|
|
@ -87,7 +87,7 @@ fn do_import_ios_bookmarks(places_api: &PlacesApi, ios_db_file_url: Url) -> Resu
|
||||||
let conn_mutex = places_api.get_sync_connection()?;
|
let conn_mutex = places_api.get_sync_connection()?;
|
||||||
let conn = conn_mutex.lock();
|
let conn = conn_mutex.lock();
|
||||||
|
|
||||||
let scope = conn.begin_interrupt_scope();
|
let scope = conn.begin_interrupt_scope()?;
|
||||||
|
|
||||||
sql_fns::define_functions(&conn)?;
|
sql_fns::define_functions(&conn)?;
|
||||||
|
|
||||||
|
|
|
@ -316,7 +316,7 @@ fn bookmark_from_row(row: &Row<'_>) -> Result<Option<BookmarkData>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn search_bookmarks(db: &PlacesDb, search: &str, limit: u32) -> Result<Vec<BookmarkData>> {
|
pub fn search_bookmarks(db: &PlacesDb, search: &str, limit: u32) -> Result<Vec<BookmarkData>> {
|
||||||
let scope = db.begin_interrupt_scope();
|
let scope = db.begin_interrupt_scope()?;
|
||||||
Ok(db
|
Ok(db
|
||||||
.query_rows_into_cached::<Vec<Option<BookmarkData>>, _, _, _>(
|
.query_rows_into_cached::<Vec<Option<BookmarkData>>, _, _, _>(
|
||||||
&SEARCH_QUERY,
|
&SEARCH_QUERY,
|
||||||
|
@ -332,7 +332,7 @@ pub fn search_bookmarks(db: &PlacesDb, search: &str, limit: u32) -> Result<Vec<B
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn recent_bookmarks(db: &PlacesDb, limit: u32) -> Result<Vec<BookmarkData>> {
|
pub fn recent_bookmarks(db: &PlacesDb, limit: u32) -> Result<Vec<BookmarkData>> {
|
||||||
let scope = db.begin_interrupt_scope();
|
let scope = db.begin_interrupt_scope()?;
|
||||||
Ok(db
|
Ok(db
|
||||||
.query_rows_into_cached::<Vec<Option<BookmarkData>>, _, _, _>(
|
.query_rows_into_cached::<Vec<Option<BookmarkData>>, _, _, _>(
|
||||||
&RECENT_BOOKMARKS_QUERY,
|
&RECENT_BOOKMARKS_QUERY,
|
||||||
|
|
|
@ -531,7 +531,7 @@ pub fn fetch_tree(
|
||||||
LEFT JOIN moz_places h ON h.id = d.fk
|
LEFT JOIN moz_places h ON h.id = d.fk
|
||||||
ORDER BY d.level, d.parent, d.position"#;
|
ORDER BY d.level, d.parent, d.position"#;
|
||||||
|
|
||||||
let scope = db.begin_interrupt_scope();
|
let scope = db.begin_interrupt_scope()?;
|
||||||
|
|
||||||
let mut stmt = db.conn().prepare(sql)?;
|
let mut stmt = db.conn().prepare(sql)?;
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ log = "0.4"
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
interrupt-support = { path = "../interrupt" }
|
interrupt-support = { path = "../interrupt" }
|
||||||
ffi-support = "0.4"
|
ffi-support = "0.4"
|
||||||
|
parking_lot = "0.5"
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
tempfile = "3.1.0"
|
tempfile = "3.1.0"
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
use crate::in_shutdown;
|
||||||
use ffi_support::implement_into_ffi_by_pointer;
|
use ffi_support::implement_into_ffi_by_pointer;
|
||||||
use interrupt_support::Interruptee;
|
use interrupt_support::Interruptee;
|
||||||
use rusqlite::InterruptHandle;
|
use rusqlite::InterruptHandle;
|
||||||
|
@ -55,11 +56,27 @@ pub struct SqlInterruptScope {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SqlInterruptScope {
|
impl SqlInterruptScope {
|
||||||
|
/// Create a new `SqlInterruptScope`
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new(ptr: Arc<AtomicUsize>) -> Self {
|
pub fn new(ptr: Arc<AtomicUsize>) -> Self {
|
||||||
let start_value = ptr.load(Ordering::SeqCst);
|
let start_value = ptr.load(Ordering::SeqCst);
|
||||||
Self { start_value, ptr }
|
Self { start_value, ptr }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new `SqlInterruptScope`, checking if we're in shutdown mode
|
||||||
|
///
|
||||||
|
/// If we're in shutdown mode, then this will return `Err(interrupt_support::Interrupted)`
|
||||||
|
#[inline]
|
||||||
|
pub fn new_with_shutdown_check(
|
||||||
|
ptr: Arc<AtomicUsize>,
|
||||||
|
) -> Result<Self, interrupt_support::Interrupted> {
|
||||||
|
if in_shutdown() {
|
||||||
|
Err(interrupt_support::Interrupted)
|
||||||
|
} else {
|
||||||
|
Ok(Self::new(ptr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Add this as an inherent method to reduce the amount of things users have to bring in.
|
/// Add this as an inherent method to reduce the amount of things users have to bring in.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn err_if_interrupted(&self) -> Result<(), interrupt_support::Interrupted> {
|
pub fn err_if_interrupted(&self) -> Result<(), interrupt_support::Interrupted> {
|
||||||
|
|
|
@ -12,6 +12,7 @@ mod maybe_cached;
|
||||||
pub mod open_database;
|
pub mod open_database;
|
||||||
mod query_plan;
|
mod query_plan;
|
||||||
mod repeat;
|
mod repeat;
|
||||||
|
mod shutdown;
|
||||||
|
|
||||||
pub use crate::conn_ext::*;
|
pub use crate::conn_ext::*;
|
||||||
pub use crate::each_chunk::*;
|
pub use crate::each_chunk::*;
|
||||||
|
@ -19,6 +20,7 @@ pub use crate::interrupt::*;
|
||||||
pub use crate::maybe_cached::*;
|
pub use crate::maybe_cached::*;
|
||||||
pub use crate::query_plan::*;
|
pub use crate::query_plan::*;
|
||||||
pub use crate::repeat::*;
|
pub use crate::repeat::*;
|
||||||
|
pub use crate::shutdown::*;
|
||||||
|
|
||||||
/// In PRAGMA foo='bar', `'bar'` must be a constant string (it cannot be a
|
/// In PRAGMA foo='bar', `'bar'` must be a constant string (it cannot be a
|
||||||
/// bound parameter), so we need to escape manually. According to
|
/// bound parameter), so we need to escape manually. According to
|
||||||
|
|
|
@ -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/. */
|
||||||
|
|
||||||
|
use interrupt_support::Interruptee;
|
||||||
|
/// Shutdown handling for database operations
|
||||||
|
///
|
||||||
|
/// This crate allows us to enter shutdown mode, causing all `SqlInterruptScope` instances that opt-in to
|
||||||
|
/// shutdown support 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 `shutdown::register_interrupt_handle()` on creation
|
||||||
|
///
|
||||||
|
/// See `PlacesDb::begin_interrupt_scope()` and `PlacesApi::new_connection()` for an example of
|
||||||
|
/// how this works.
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,6 +14,7 @@ path = "src/places-utils.rs"
|
||||||
places = { path = "../../components/places" }
|
places = { path = "../../components/places" }
|
||||||
sync-guid = { path = "../../components/support/guid" }
|
sync-guid = { path = "../../components/support/guid" }
|
||||||
types = { path = "../../components/support/types" }
|
types = { path = "../../components/support/types" }
|
||||||
|
sql-support = { path = "../../components/support/sql" }
|
||||||
sync15 = { path = "../../components/sync15" }
|
sync15 = { path = "../../components/sync15" }
|
||||||
viaduct-reqwest = { path = "../../components/support/viaduct-reqwest" }
|
viaduct-reqwest = { path = "../../components/support/viaduct-reqwest" }
|
||||||
serde = "1"
|
serde = "1"
|
||||||
|
|
|
@ -163,7 +163,6 @@ fn run_native_export(db: &PlacesDb, filename: String) -> Result<()> {
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn sync(
|
fn sync(
|
||||||
api: &PlacesApi,
|
|
||||||
mut engine_names: Vec<String>,
|
mut engine_names: Vec<String>,
|
||||||
cred_file: String,
|
cred_file: String,
|
||||||
wipe_all: bool,
|
wipe_all: bool,
|
||||||
|
@ -227,7 +226,7 @@ fn sync(
|
||||||
&mut mem_cached_state,
|
&mut mem_cached_state,
|
||||||
&cli_fxa.client_init.clone(),
|
&cli_fxa.client_init.clone(),
|
||||||
&cli_fxa.root_sync_key,
|
&cli_fxa.root_sync_key,
|
||||||
&api.dummy_sync_interrupt_scope(),
|
&sql_support::ShutdownInterruptee,
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -370,6 +369,12 @@ fn main() -> Result<()> {
|
||||||
// Needed to make the get_registered_sync_engine() calls work.
|
// Needed to make the get_registered_sync_engine() calls work.
|
||||||
api.clone().register_with_sync_manager();
|
api.clone().register_with_sync_manager();
|
||||||
|
|
||||||
|
ctrlc::set_handler(move || {
|
||||||
|
println!("\nCTRL-C detected, enabling shutdown mode\n");
|
||||||
|
sql_support::shutdown();
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
match opts.cmd {
|
match opts.cmd {
|
||||||
Command::Sync {
|
Command::Sync {
|
||||||
engines,
|
engines,
|
||||||
|
@ -380,7 +385,6 @@ fn main() -> Result<()> {
|
||||||
nsyncs,
|
nsyncs,
|
||||||
wait,
|
wait,
|
||||||
} => sync(
|
} => sync(
|
||||||
&api,
|
|
||||||
engines,
|
engines,
|
||||||
credential_file,
|
credential_file,
|
||||||
wipe_all,
|
wipe_all,
|
||||||
|
|
Загрузка…
Ссылка в новой задаче