Skip to content

Commit e91748e

Browse files
authored
Merge pull request #72 from saagpatel/codex/fix/kb-watcher-mutex
fix(src): close KB watcher start/start TOCTOU race
2 parents aee527a + 9509faf commit e91748e

1 file changed

Lines changed: 23 additions & 8 deletions

File tree

src-tauri/src/commands/kb_commands.rs

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -260,17 +260,32 @@ pub(crate) async fn start_kb_watcher_impl(
260260

261261
let validated_path = validate_stored_kb_path(&folder_path)?;
262262

263-
// Create and start watcher
264-
let mut watcher = KbWatcher::new(&validated_path).map_err(|e| e.to_string())?;
265-
let mut rx = watcher.start().map_err(|e| e.to_string())?;
266-
267-
// Store watcher instance
268-
{
263+
// Acquire the global watcher slot up-front, then create and start the
264+
// watcher *inside* the critical section. This closes a TOCTOU race
265+
// where two concurrent start_kb_watcher_impl calls could each
266+
// successfully construct + start their own KbWatcher instance before
267+
// either reached the KB_WATCHER.lock() — one of the instances would
268+
// then be overwritten in the slot while still running on the
269+
// filesystem as an orphan notify thread. With the lock held for the
270+
// full init sequence, a racing second starter observes the populated
271+
// slot and returns Ok(false) without ever creating its own watcher.
272+
//
273+
// KbWatcher::new and watcher.start() are synchronous — no .await is
274+
// performed while the StdMutex guard is held, so this is safe under
275+
// tokio's work-stealing scheduler.
276+
let mut rx = {
269277
let mut guard = KB_WATCHER.lock().map_err(|e| e.to_string())?;
278+
if guard.is_some() {
279+
return Ok(false);
280+
}
281+
let mut watcher = KbWatcher::new(&validated_path).map_err(|e| e.to_string())?;
282+
let rx = watcher.start().map_err(|e| e.to_string())?;
270283
*guard = Some(watcher);
271-
}
284+
rx
285+
};
272286

273-
// Spawn event handler
287+
// Spawn event handler outside the lock so receiving doesn't starve
288+
// other KB commands.
274289
let window_clone = window.clone();
275290
tokio::spawn(async move {
276291
while let Some(event) = rx.recv().await {

0 commit comments

Comments
 (0)