Compare commits

...
Author SHA1 Message Date
Claude e0d77b3424 style: fix formatting
https://claude.ai/code/session_01GG37cPSH8vfi9nriukuorf
2026-03-23 13:12:23 +00:00
ZakiandClaude Opus 4.6 e1754ce8c5 perf(agent): reduce lock contention in thread resolution (#1489)
Consolidate two separate read-lock acquisitions into one in
resolve_thread(). The write-lock upgrade still uses double-checked
locking but the read path is now a single acquisition.

Closes #1489

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
2026-03-23 05:57:10 -07:00
+83 -36
View File
@@ -102,6 +102,9 @@ impl SessionManager {
/// Resolve an external thread ID to an internal thread. /// Resolve an external thread ID to an internal thread.
/// ///
/// Returns the session and thread ID. Creates both if they don't exist. /// Returns the session and thread ID. Creates both if they don't exist.
///
/// Uses a single read-lock acquisition for both the key lookup and the UUID
/// adoption check to reduce contention under concurrent approval load.
pub async fn resolve_thread( pub async fn resolve_thread(
&self, &self,
user_id: &str, user_id: &str,
@@ -116,51 +119,57 @@ impl SessionManager {
external_thread_id: external_thread_id.map(String::from), external_thread_id: external_thread_id.map(String::from),
}; };
// Check if we have a mapping // Parse UUID once outside the lock (if applicable)
{ let ext_uuid = external_thread_id.and_then(|s| Uuid::parse_str(s).ok());
// Single read lock for both the key lookup and UUID adoption check
let adoptable_uuid = {
let thread_map = self.thread_map.read().await; let thread_map = self.thread_map.read().await;
// Fast path: exact key match
if let Some(&thread_id) = thread_map.get(&key) { if let Some(&thread_id) = thread_map.get(&key) {
// Verify thread still exists in session
let sess = session.lock().await; let sess = session.lock().await;
if sess.threads.contains_key(&thread_id) { if sess.threads.contains_key(&thread_id) {
return (Arc::clone(&session), thread_id); return (Arc::clone(&session), thread_id);
} }
} }
}
// Check if external_thread_id is itself a known thread UUID that // UUID adoption check (still under the same read lock).
// exists in the session but was never registered in the thread_map // If external_thread_id is a valid UUID not mapped elsewhere,
// (e.g. created by chat_new_thread_handler or hydrated from DB). // it may be a thread created by chat_new_thread_handler or
// We only adopt it if no thread_map entry maps to this UUID — // hydrated from DB that we can adopt.
// otherwise it belongs to a different channel scope. if let Some(ext_uuid) = ext_uuid {
if let Some(ext_tid) = external_thread_id let mapped_elsewhere = thread_map.values().any(|&v| v == ext_uuid);
&& let Ok(ext_uuid) = Uuid::parse_str(ext_tid) if !mapped_elsewhere {
{ Some(ext_uuid)
let thread_map = self.thread_map.read().await; } else {
let mapped_elsewhere = thread_map.values().any(|&v| v == ext_uuid); None
drop(thread_map);
if !mapped_elsewhere {
let sess = session.lock().await;
if sess.threads.contains_key(&ext_uuid) {
drop(sess);
let mut thread_map = self.thread_map.write().await;
// Re-check after acquiring write lock to prevent race condition
// where another task mapped this UUID between our read and write.
if !thread_map.values().any(|&v| v == ext_uuid) {
thread_map.insert(key, ext_uuid);
drop(thread_map);
// Ensure undo manager exists
let mut undo_managers = self.undo_managers.write().await;
undo_managers
.entry(ext_uuid)
.or_insert_with(|| Arc::new(Mutex::new(UndoManager::new())));
return (session, ext_uuid);
}
// If it was mapped elsewhere while we were unlocked, fall through
// to create a new thread, preserving channel isolation.
} }
} else {
None
}
}; // Single read lock dropped here
// If we found an adoptable UUID, verify it exists in session and acquire write lock
if let Some(ext_uuid) = adoptable_uuid {
let sess = session.lock().await;
if sess.threads.contains_key(&ext_uuid) {
drop(sess);
let mut thread_map = self.thread_map.write().await;
// Re-check after acquiring write lock to prevent race condition
// where another task mapped this UUID between our read and write.
if !thread_map.values().any(|&v| v == ext_uuid) {
thread_map.insert(key, ext_uuid);
drop(thread_map);
// Ensure undo manager exists
let mut undo_managers = self.undo_managers.write().await;
undo_managers
.entry(ext_uuid)
.or_insert_with(|| Arc::new(Mutex::new(UndoManager::new())));
return (session, ext_uuid);
}
// If mapped elsewhere while unlocked, fall through to create new thread
} }
} }
@@ -909,6 +918,44 @@ mod tests {
} }
} }
#[tokio::test]
async fn test_resolve_thread_consolidates_read_path() {
// Verify that resolve_thread still correctly handles:
// 1. Fast path: key exists in thread_map
// 2. UUID adoption: external_thread_id is a UUID in session but not in map
// 3. New thread: neither path matches
use crate::agent::session::Thread;
let manager = SessionManager::new();
// Case 1: Normal resolution creates thread and maps it
let (session1, tid1) = manager
.resolve_thread("user1", "chan1", Some("ext-1"))
.await;
// Resolving again with same key should return same thread (fast path)
let (_, tid1_again) = manager
.resolve_thread("user1", "chan1", Some("ext-1"))
.await;
assert_eq!(tid1, tid1_again);
// Case 2: UUID adoption - insert a thread directly into session
let adopted_id = Uuid::new_v4();
{
let mut sess = session1.lock().await;
let thread = Thread::with_id(adopted_id, sess.id);
sess.threads.insert(adopted_id, thread);
}
// Resolve with the UUID as external_thread_id -- should adopt it
let (_, resolved) = manager
.resolve_thread("user1", "chan1", Some(&adopted_id.to_string()))
.await;
assert_eq!(resolved, adopted_id);
// Case 3: Different channel gets different thread
let (_, tid2) = manager.resolve_thread("user1", "chan2", None).await;
assert_ne!(tid1, tid2);
}
#[tokio::test] #[tokio::test]
async fn test_resolve_thread_finds_existing_session_thread_by_uuid() { async fn test_resolve_thread_finds_existing_session_thread_by_uuid() {
use crate::agent::session::{Session, Thread}; use crate::agent::session::{Session, Thread};