From 1bbb9c233c4934cc6d0c5814c058f1a0939fff5c Mon Sep 17 00:00:00 2001 From: benthecarman Date: Tue, 20 Oct 2026 14:35:10 -0500 Subject: [PATCH] Clean up lock entries for rejected KV writes SqliteStore and PostgresStore register a per-key lock entry before validating the namespace and key, but only remove it after a completed write. Writes and removes rejected by validation therefore left their entry behind, slowly growing the lock map for each distinct invalid key in release builds. Clean up the lock entry when validation fails. This was found by Project Loupe. This change was made with the help of Claude Code. --- src/io/postgres_store/mod.rs | 17 +++++++++++++++-- src/io/sqlite_store/mod.rs | 17 +++++++++++++++-- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/io/postgres_store/mod.rs b/src/io/postgres_store/mod.rs index 241df19cf8..a80fec2952 100644 --- a/src/io/postgres_store/mod.rs +++ b/src/io/postgres_store/mod.rs @@ -854,7 +854,12 @@ impl PostgresStoreInner { &self, inner_lock_ref: Arc>, locking_key: String, version: u64, primary_namespace: &str, secondary_namespace: &str, key: &str, buf: Vec, ) -> io::Result<()> { - check_namespace_key_validity(primary_namespace, secondary_namespace, Some(key), "write")?; + if let Err(e) = + check_namespace_key_validity(primary_namespace, secondary_namespace, Some(key), "write") + { + self.clean_locks(&inner_lock_ref, locking_key); + return Err(e.into()); + } self.execute_locked_write(inner_lock_ref, locking_key, version, async move || { let kv_table = &self.kv_table_name_sql; @@ -914,7 +919,15 @@ impl PostgresStoreInner { &self, inner_lock_ref: Arc>, locking_key: String, version: u64, primary_namespace: &str, secondary_namespace: &str, key: &str, ) -> io::Result<()> { - check_namespace_key_validity(primary_namespace, secondary_namespace, Some(key), "remove")?; + if let Err(e) = check_namespace_key_validity( + primary_namespace, + secondary_namespace, + Some(key), + "remove", + ) { + self.clean_locks(&inner_lock_ref, locking_key); + return Err(e.into()); + } self.execute_locked_write(inner_lock_ref, locking_key, version, async move || { let kv_table = &self.kv_table_name_sql; diff --git a/src/io/sqlite_store/mod.rs b/src/io/sqlite_store/mod.rs index 8112140a73..be7cb06b45 100644 --- a/src/io/sqlite_store/mod.rs +++ b/src/io/sqlite_store/mod.rs @@ -429,7 +429,12 @@ impl SqliteStoreInner { &self, inner_lock_ref: Arc>, locking_key: String, version: u64, primary_namespace: &str, secondary_namespace: &str, key: &str, buf: Vec, ) -> io::Result<()> { - check_namespace_key_validity(primary_namespace, secondary_namespace, Some(key), "write")?; + if let Err(e) = + check_namespace_key_validity(primary_namespace, secondary_namespace, Some(key), "write") + { + self.clean_locks(&inner_lock_ref, locking_key); + return Err(e.into()); + } self.execute_locked_write(inner_lock_ref, locking_key, version, || { let locked_conn = self.connection.lock().expect("lock"); @@ -473,7 +478,15 @@ impl SqliteStoreInner { &self, inner_lock_ref: Arc>, locking_key: String, version: u64, primary_namespace: &str, secondary_namespace: &str, key: &str, ) -> io::Result<()> { - check_namespace_key_validity(primary_namespace, secondary_namespace, Some(key), "remove")?; + if let Err(e) = check_namespace_key_validity( + primary_namespace, + secondary_namespace, + Some(key), + "remove", + ) { + self.clean_locks(&inner_lock_ref, locking_key); + return Err(e.into()); + } self.execute_locked_write(inner_lock_ref, locking_key, version, || { let locked_conn = self.connection.lock().expect("lock");