Skip to content

Commit dd04636

Browse files
committed
feat: close dbs when about to exit
- This will close connections, truncate WAL files and drain the db cache
1 parent 868ae17 commit dd04636

3 files changed

Lines changed: 103 additions & 7 deletions

File tree

Cargo.lock

Lines changed: 18 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,12 @@ serde = { version = "1.0.228", features = ["derive"] }
1919
serde_json = "1.0.145"
2020
thiserror = "2.0.17"
2121
log = "0.4.28"
22-
futures-core = "0.3.31"
22+
futures = "0.3.31"
2323
time = "0.3.44"
24-
tokio = { version = "1.48.0", features = ["sync"] }
24+
tokio = { version = "1.48.0", features = ["sync", "time"] }
2525
indexmap = { version = "2.12.1", features = ["serde"] }
2626
base64 = "0.22.1"
27+
tracing = { version = "0.1.41", default-features = false, features = ["std", "release_max_level_off"] }
2728

2829
# SQLx for types and queries (time feature enables datetime type decoding)
2930
sqlx = { version = "0.8.6", features = ["sqlite", "json", "time", "runtime-tokio"] }

src/lib.rs

Lines changed: 82 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
use std::collections::HashMap;
2+
use std::sync::Arc;
23

3-
use tauri::{Manager, Runtime, plugin::Builder as PluginBuilder};
4+
use tauri::{Manager, RunEvent, Runtime, plugin::Builder as PluginBuilder};
45
use tokio::sync::RwLock;
6+
use tracing::{debug, error, info, warn};
57

68
mod commands;
79
mod decode;
@@ -15,8 +17,8 @@ pub use wrapper::{DatabaseWrapper, WriteQueryResult};
1517
///
1618
/// This struct maintains a thread-safe map of database paths to their corresponding
1719
/// connection wrappers.
18-
#[derive(Default)]
19-
pub struct DbInstances(pub RwLock<HashMap<String, DatabaseWrapper>>);
20+
#[derive(Clone, Default)]
21+
pub struct DbInstances(pub Arc<RwLock<HashMap<String, DatabaseWrapper>>>);
2022

2123
/// Builder for the SQLite plugin.
2224
///
@@ -57,10 +59,86 @@ impl Builder {
5759
])
5860
.setup(|app, _api| {
5961
app.manage(DbInstances::default());
62+
debug!("SQLite plugin initialized");
6063
// Future PR: Possibly handle migrations here
61-
// Future PR: Cleanup on app exit
6264
Ok(())
6365
})
66+
.on_event(|app, event| {
67+
match event {
68+
RunEvent::ExitRequested { api, code, .. } => {
69+
info!("App exit requested (code: {:?}) - closing databases before exit", code);
70+
71+
// Prevent immediate exit so we can close connections and checkpoint WAL
72+
api.prevent_exit();
73+
74+
let app_handle = app.clone();
75+
76+
let handle = match tokio::runtime::Handle::try_current() {
77+
Ok(h) => h,
78+
Err(_) => {
79+
warn!("No tokio runtime available for database cleanup");
80+
app_handle.exit(code.unwrap_or(0));
81+
return;
82+
}
83+
};
84+
85+
let instances = app.state::<DbInstances>().inner().clone();
86+
87+
// Spawn a blocking thread to close databases
88+
// (block_in_place panics on current_thread runtime)
89+
let cleanup_result = std::thread::spawn(move || {
90+
handle.block_on(async {
91+
let mut guard = instances.0.write().await;
92+
let wrappers: Vec<DatabaseWrapper> =
93+
guard.drain().map(|(_, v)| v).collect();
94+
95+
// Close databases in parallel with timeout
96+
let closes = wrappers.into_iter().map(|w| w.close());
97+
let results = tokio::time::timeout(
98+
std::time::Duration::from_secs(5),
99+
futures::future::join_all(closes),
100+
)
101+
.await;
102+
103+
match results {
104+
Ok(outcomes) => {
105+
for result in outcomes {
106+
if let Err(e) = result {
107+
warn!("Error closing database: {:?}", e);
108+
}
109+
}
110+
}
111+
Err(_) => warn!("Database cleanup timed out after 5 seconds"),
112+
}
113+
114+
debug!("Database cleanup complete");
115+
})
116+
})
117+
.join();
118+
119+
if let Err(e) = cleanup_result {
120+
error!("Database cleanup thread panicked: {:?}", e);
121+
}
122+
123+
app_handle.exit(code.unwrap_or(0));
124+
}
125+
RunEvent::Exit => {
126+
// ExitRequested should have already closed all databases
127+
// This is just a safety check
128+
let instances = app.state::<DbInstances>();
129+
if let Ok(instances) = instances.0.try_read() {
130+
if !instances.is_empty() {
131+
warn!("Exit event fired with {} database(s) still open - cleanup may have been skipped", instances.len());
132+
} else {
133+
debug!("Exit event: all databases already closed");
134+
}
135+
}
136+
}
137+
_ => {
138+
// Other events don't require action
139+
}
140+
}
141+
})
64142
.build()
65143
}
66144
}

0 commit comments

Comments
 (0)