From 964e13f124ad1feeb93c10168b265dc4936f738c Mon Sep 17 00:00:00 2001 From: Tony <68118705+Legend-Master@users.noreply.github.com> Date: Sat, 18 Apr 2026 00:45:16 +0800 Subject: [PATCH] fix(store): dead lock trying to set while exiting (#3395) * fix(store): dead lock trying to set while exiting * Add change file --- .changes/store-set-exit-deadlock.md | 6 ++++++ plugins/store/src/lib.rs | 12 ++++++------ plugins/store/src/store.rs | 8 ++++---- 3 files changed, 16 insertions(+), 10 deletions(-) create mode 100644 .changes/store-set-exit-deadlock.md diff --git a/.changes/store-set-exit-deadlock.md b/.changes/store-set-exit-deadlock.md new file mode 100644 index 000000000..ec82f2abc --- /dev/null +++ b/.changes/store-set-exit-deadlock.md @@ -0,0 +1,6 @@ +--- +store: patch +store-js: patch +--- + +Fix a deadlock when calling `Store::set` while exiting (on `RunEvent::Exit`) diff --git a/plugins/store/src/lib.rs b/plugins/store/src/lib.rs index 0223f9c21..458bc4b02 100644 --- a/plugins/store/src/lib.rs +++ b/plugins/store/src/lib.rs @@ -15,7 +15,7 @@ pub use serde_json::Value as JsonValue; use std::{ collections::HashMap, path::{Path, PathBuf}, - sync::{Arc, Mutex}, + sync::{Arc, RwLock}, time::Duration, }; pub use store::{resolve_store_path, DeserializeFn, SerializeFn, Store, StoreBuilder}; @@ -39,7 +39,7 @@ struct ChangePayload<'a> { #[derive(Debug)] struct StoreState { - stores: Arc>>, + stores: Arc>>, serialize_fns: HashMap, deserialize_fns: HashMap, default_serialize: SerializeFn, @@ -139,7 +139,7 @@ async fn get_store( store_state: State<'_, StoreState>, path: PathBuf, ) -> Result> { - let stores = store_state.stores.lock().unwrap(); + let stores = store_state.stores.read().unwrap(); Ok(stores.get(&resolve_store_path(&app, path)?).copied()) } @@ -317,7 +317,7 @@ impl> StoreExt for T { fn get_store(&self, path: impl AsRef) -> Option>> { let collection = self.state::(); - let stores = collection.stores.lock().unwrap(); + let stores = collection.stores.read().unwrap(); stores .get(&resolve_store_path(self.app_handle(), path.as_ref()).ok()?) .and_then(|rid| self.resources_table().get(*rid).ok()) @@ -437,7 +437,7 @@ impl Builder { ]) .setup(move |app_handle, _api| { app_handle.manage(StoreState { - stores: Arc::new(Mutex::new(HashMap::new())), + stores: Arc::new(RwLock::new(HashMap::new())), serialize_fns: self.serialize_fns, deserialize_fns: self.deserialize_fns, default_serialize: self.default_serialize, @@ -448,7 +448,7 @@ impl Builder { .on_event(|app_handle, event| { if let RunEvent::Exit = event { let collection = app_handle.state::(); - let stores = collection.stores.lock().unwrap(); + let stores = collection.stores.read().unwrap(); for (path, rid) in stores.iter() { if let Ok(store) = app_handle.resources_table().get::>(*rid) { if let Err(err) = store.save() { diff --git a/plugins/store/src/store.rs b/plugins/store/src/store.rs index a6b5febd2..088dcc240 100644 --- a/plugins/store/src/store.rs +++ b/plugins/store/src/store.rs @@ -188,7 +188,7 @@ impl StoreBuilder { pub(crate) fn build_inner(mut self) -> crate::Result<(Arc>, ResourceId)> { let stores = self.app.state::().stores.clone(); - let mut stores = stores.lock().unwrap(); + let mut stores = stores.write().unwrap(); self.path = resolve_store_path(&self.app, self.path)?; @@ -403,7 +403,7 @@ impl StoreInner { fn emit_change_event(&self, key: &str, value: Option<&JsonValue>) -> crate::Result<()> { let state = self.app.state::(); - let stores = state.stores.lock().unwrap(); + let stores = state.stores.read().unwrap(); let exists = value.is_some(); self.app.emit( "store://change", @@ -438,7 +438,7 @@ impl Resource for Store { fn close(self: Arc) { let store = self.store.lock().unwrap(); let state = store.app.state::(); - let mut stores = state.stores.lock().unwrap(); + let mut stores = state.stores.write().unwrap(); stores.remove(&store.path); } } @@ -554,7 +554,7 @@ impl Store { let store = self.store.lock().unwrap(); let app = store.app.clone(); let state = app.state::(); - let stores = state.stores.lock().unwrap(); + let stores = state.stores.read().unwrap(); if let Some(rid) = stores.get(&store.path).copied() { drop(store); drop(stores);