Files
codex/codex-rs/memories/write/src/workspace_lock.rs
T
jif 90894a780d Prevent memory imports from racing with consolidation (#56462)
## Why

Concurrent imports and consolidation can modify the same memory workspace, leaving a partial import across memory versions or interfering with consolidation snapshots and baselines.

## What changed

- Add a cross-process workspace lock shared by memory imports and consolidation.
- Acquire locks for every import destination before modifying any workspace. Return a retryable busy error if a destination is locked, and skip consolidation when its workspace is busy.
- Hold locks through import completion even if the caller drops the future, and through consolidation agent termination and baseline finalization.

## Testing

Add an app-server integration test that blocks an active consolidation, verifies import rejection without partial writes or snapshot changes, then retries successfully and confirms imported resources remain visible to the next consolidation.

GitOrigin-RevId: c4ce945f916dcb4d75a3190ea869751fac51de83
2026-10-23 12:23:10 +00:00

36 lines
1.3 KiB
Rust

//! Cross-process exclusion between memory imports and consolidation.
//! Lock files live beside memory roots and must not be removed or replaced.
use std::fs::File;
use std::fs::OpenOptions;
use std::fs::TryLockError;
use std::io;
use std::path::Path;
/// Keeps a memory workspace exclusively owned until all writers have stopped.
pub struct MemoryWorkspaceLock {
_file: File,
}
impl MemoryWorkspaceLock {
/// Returns `WouldBlock` when another import or consolidation owns this root.
pub fn try_acquire(memory_root: &Path) -> io::Result<Self> {
// Keep this outside the workspace: baseline resets and memory resets must
// not replace the inode while another process still owns its lock.
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(false)
.open(memory_root.with_extension("lock"))?;
match file.try_lock() {
Ok(()) => Ok(Self { _file: file }),
Err(TryLockError::WouldBlock) => Err(io::Error::new(
io::ErrorKind::WouldBlock,
"memory workspace is busy; retry after consolidation or another import finishes",
)),
Err(TryLockError::Error(error)) => Err(error),
}
}
}