11use 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 } ;
45use tokio:: sync:: RwLock ;
6+ use tracing:: { debug, error, info, warn} ;
57
68mod commands;
79mod 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