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,99 @@ 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 mut set = tokio:: task:: JoinSet :: new ( ) ;
97+ for wrapper in wrappers {
98+ set. spawn ( async move { wrapper. close ( ) . await } ) ;
99+ }
100+
101+ let timeout_result = tokio:: time:: timeout (
102+ std:: time:: Duration :: from_secs ( 5 ) ,
103+ async {
104+ while let Some ( result) = set. join_next ( ) . await {
105+ match result {
106+ Ok ( Err ( e) ) => warn ! ( "Error closing database: {:?}" , e) ,
107+ Err ( e) => warn ! ( "Database close task panicked: {:?}" , e) ,
108+ Ok ( Ok ( ( ) ) ) => { }
109+ }
110+ }
111+ } ,
112+ )
113+ . await ;
114+
115+ if timeout_result. is_err ( ) {
116+ warn ! ( "Database cleanup timed out after 5 seconds" ) ;
117+ } else {
118+ debug ! ( "Database cleanup complete" ) ;
119+ }
120+ } )
121+ } )
122+ . join ( ) ;
123+
124+ if let Err ( e) = cleanup_result {
125+ error ! ( "Database cleanup thread panicked: {:?}" , e) ;
126+ }
127+
128+ app_handle. exit ( code. unwrap_or ( 0 ) ) ;
129+ }
130+ RunEvent :: Exit => {
131+ // ExitRequested should have already closed all databases
132+ // This is just a safety check
133+ let instances = app. state :: < DbInstances > ( ) ;
134+ match instances. 0 . try_read ( ) {
135+ Ok ( guard) => {
136+ if !guard. is_empty ( ) {
137+ warn ! (
138+ "Exit event fired with {} database(s) still open - cleanup may have been skipped" ,
139+ guard. len( )
140+ ) ;
141+ } else {
142+ debug ! ( "Exit event: all databases already closed" ) ;
143+ }
144+ }
145+ Err ( _) => {
146+ warn ! ( "Exit event: could not check database state (lock held - cleanup may still be in progress)" ) ;
147+ }
148+ }
149+ }
150+ _ => {
151+ // Other events don't require action
152+ }
153+ }
154+ } )
64155 . build ( )
65156 }
66157}
0 commit comments