refactor: improve macOS event handling and TDLib shutdown process

- Updated the event handling in the Tauri application to use a match statement for better clarity and maintainability.
- Added graceful shutdown logic for TDLib during application exit to prevent crashes related to C++ destructors.
- Enhanced the TDLib manager to signal shutdown and exit the receive loop cleanly, improving application stability.
This commit is contained in:
Steven Enamakel
2026-02-05 16:21:03 +05:30
parent 532eae1bdc
commit 29d62544d6
3 changed files with 45 additions and 14 deletions
+26 -8
View File
@@ -574,14 +574,32 @@ pub fn run() {
.build(tauri::generate_context!())
.expect("error while building tauri application")
.run(|app_handle, event| {
// Handle macOS Dock icon click (reopen event)
#[cfg(target_os = "macos")]
if let RunEvent::Reopen { .. } = event {
show_main_window(app_handle);
}
match event {
// Handle macOS Dock icon click (reopen event)
#[cfg(target_os = "macos")]
RunEvent::Reopen { .. } => {
show_main_window(app_handle);
}
// Suppress unused variable warnings on other platforms
#[cfg(not(target_os = "macos"))]
let _ = (app_handle, event);
// Gracefully shut down TDLib before process exit to prevent
// use-after-free crash in the blocking receive loop.
#[cfg(not(any(target_os = "android", target_os = "ios")))]
RunEvent::Exit => {
log::info!("[app] Exit event received, shutting down TDLib");
use crate::services::tdlib::TDLIB_MANAGER;
// Signal the TDLib worker to stop. The blocking receive() call
// has a 2-second internal timeout, so we must wait long enough
// for any in-flight call to finish before process teardown runs
// C++ destructors on TDLib's internal state.
TDLIB_MANAGER.signal_shutdown();
std::thread::sleep(std::time::Duration::from_millis(2500));
log::info!("[app] TDLib shutdown wait complete");
let _ = app_handle;
}
_ => {
let _ = app_handle;
}
}
});
}
+18 -5
View File
@@ -179,14 +179,20 @@ impl TdLibManager {
let poll_handle = tokio::spawn(async move {
loop {
if !state_clone.is_active.load(Ordering::SeqCst) {
log::info!("[tdlib] Receive loop exiting (shutdown signalled)");
break;
}
// Use spawn_blocking for the blocking TDLib receive call
let receive_result = tokio::task::spawn_blocking(|| {
// Use a short timeout to be more responsive
// Note: tdlib_rs::receive() uses 2.0s internally, but that's OK
// because we're in spawn_blocking and won't block the async runtime
// Clone the Arc so we can check is_active inside
// spawn_blocking *before* entering the 2-second blocking
// td_receive() FFI call. This minimises the window where
// a process-exit could tear down TDLib state while we're
// inside the C++ code.
let state_for_recv = state_clone.clone();
let receive_result = tokio::task::spawn_blocking(move || {
if !state_for_recv.is_active.load(Ordering::SeqCst) {
return None;
}
tdlib_rs::receive()
})
.await;
@@ -504,6 +510,13 @@ impl TdLibManager {
Ok(())
}
/// Signal the TDLib worker to stop (non-async, safe to call from any context).
/// This is used during app exit to prevent the receive loop from crashing
/// when TDLib's C++ static destructors run during process shutdown.
pub fn signal_shutdown(&self) {
self.state.is_active.store(false, Ordering::SeqCst);
}
/// Check if the client is active.
pub fn is_active(&self) -> bool {
self.state.is_active.load(Ordering::SeqCst) && self.client_id.read().is_some()
+1 -1
View File
@@ -41,7 +41,7 @@
"macOS": {
"minimumSystemVersion": "10.15",
"dmg": {
"background": "./images/background-dmg.svg"
"background": "./images/background-dmg.png"
},
"frameworks": [
"./libraries/libtdjson.1.8.29.dylib",