mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-31 05:28:25 -06:00
Issue #99: Library: exclusive lock so two processes can't open one library
Two processes on one library each kept their own catalog and seq counter; the last snapshot silently dropped the other's edits. - lightcraft_catalog:🔒 LibraryLock holds an exclusive OS lock (std File::try_lock: flock / LockFileEx) on <library>/catalog.lock for the session; who holds it goes to catalog.lock.owner (separate file: a Windows lock also blocks reads). Released on drop and by the OS when the process dies, so leftover files from a crash never lock anyone out. File systems without lock support open unprotected with a warning. - Session::open_library takes the lock before reading anything and fails with EngineError::LibraryInUse ("this library is already open in LightCraft 0.x (process N on host)"); reopening the session's own library hands its lock over. - lightcraft-cli (mcp/run/snapshot --library) adds how to drive the running app instead (--control + mcp --connect). - Tests: in-process, leftover files, a killed child process, two engine sessions, CLI e2e (mcp --library holds it, run --library is refused). Existing tests that reopened a library while it was open now drop the first session. - docs: parity row LRC-LIB-CATALOG-LOCK, mcp.md, web.md (no guard across browser tabs yet), whats-new. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
572b55a44a
commit
ee4b78f646
@@ -91,6 +91,17 @@ USAGE:
|
||||
#[global_allocator]
|
||||
static ALLOC: dhat::Alloc = dhat::Alloc;
|
||||
|
||||
/// Why `--library DIR` can't be opened; for a library open in another program, how to work with
|
||||
/// that one instead (issue #99).
|
||||
fn library_error(dir: &str, e: lightcraft_engine::EngineError) -> String {
|
||||
match e {
|
||||
lightcraft_engine::EngineError::LibraryInUse(why) => format!(
|
||||
"{dir}: {why}\nTo work with the library while the app has it open, start the app with `--control PORT` and use `lightcraft-cli mcp --connect 127.0.0.1:PORT`."
|
||||
),
|
||||
e => format!("{dir}: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> ExitCode {
|
||||
// `--features dhat-heap`: count allocations; the profile is written when `_heap` drops
|
||||
// (LIGHTCRAFT_DHAT_FILE, default dhat-heap.json).
|
||||
@@ -182,7 +193,7 @@ fn mcp(args: &[String]) -> Result<(), String> {
|
||||
let mut h = match &library {
|
||||
Some(dir) => {
|
||||
let mut h = Headless::default();
|
||||
let r = h.session.open_library(dir, demo).map_err(|e| e.to_string())?;
|
||||
let r = h.session.open_library(dir, demo).map_err(|e| library_error(dir, e))?;
|
||||
eprintln!("lightcraft-cli mcp: opened library {dir} ({r:?})");
|
||||
h
|
||||
}
|
||||
@@ -419,7 +430,7 @@ fn run(args: &[String]) -> Result<(), String> {
|
||||
let mut h = match &library {
|
||||
Some(dir) => {
|
||||
let mut h = Headless::default();
|
||||
h.session.open_library(dir, demo).map_err(|e| e.to_string())?;
|
||||
h.session.open_library(dir, demo).map_err(|e| library_error(dir, e))?;
|
||||
h
|
||||
}
|
||||
None if demo => Headless::demo(),
|
||||
@@ -580,7 +591,7 @@ fn snapshot(args: &[String]) -> Result<(), String> {
|
||||
let mut session = match &library {
|
||||
Some(dir) => {
|
||||
let mut s = Session::new().with_fs();
|
||||
s.open_library(dir, false).map_err(|e| format!("{dir}: {e}"))?;
|
||||
s.open_library(dir, false).map_err(|e| library_error(dir, e))?;
|
||||
s
|
||||
}
|
||||
None if files.is_empty() => Session::with_demo().with_fs(),
|
||||
|
||||
Reference in New Issue
Block a user