serve sync concurrency
docs/architecture/serve-sync-concurrency.md
gbrain serve ↔ gbrain sync concurrency (PGLite)
Short version: on a PGLite brain, stop gbrain serve before a large sync.
Why
PGLite is a single-writer embedded Postgres (WASM). A running gbrain serve
(stdio or HTTP MCP) holds an open PGLite connection on the brain's data
directory. gbrain sync needs to write to that same data directory. The two
contend for PGLite's single-writer connection / write-lock — this is NOT the
gbrain-sync advisory lock (that's a separate, DB-row coordination lock for
two concurrent syncs). Confusing the two sends you debugging the wrong surface.
Symptoms of serve↔sync contention on PGLite:
gbrain syncblocks acquiring the PGLite write lock, or makes very slow progress, while agbrain serveprocess is alive on the same brain.- Killing stale
gbrain serveMCP processes frees the lock and sync proceeds.
What to do
- Stop any
gbrain serveprocess for this brain before a large sync:pkill -f 'gbrain serve' # or stop your MCP client / Claude Desktop / Cursor gbrain sync --no-pull --no-embed --yes - Restart
gbrain serveafter the sync completes.
This contention does not apply to the Postgres engine — Postgres tolerates
concurrent connections, so serve and sync can run simultaneously there.
Diagnosing a sync hang
If a sync wedges (no progress, high CPU), re-run with the per-file begin trace so the stalling file is named:
GBRAIN_SYNC_TRACE=1 gbrain sync --no-pull --no-embed --yes
The last [sync] begin import: <path> line with no following completion is the
file being processed when the hang occurred. Under --workers >1 / --all,
the stuck file is in the set of begin-lines without a matching completion.
If you suspect a schema-pack regex is the cause (a pack with a
catastrophic-backtracking inference.regex), complete the sync with the pack
disabled and re-run extraction afterward:
gbrain sync --no-schema-pack --no-pull --no-embed --yes
gbrain schema lint flags the classic nested-quantifier ReDoS shapes
((a+)+, (a*)*, …) in pack regexes as warnings.
The manual diagnosis above has an automated cousin: the progress-aware stall
watchdog. If the import drain makes no forward progress for
GBRAIN_SYNC_STALL_ABORT_SECONDS (default 900; keyed on file-import
progress, not the lock heartbeat), the run aborts with
reason: 'stall_timeout' and releases the per-source lock so the next
gbrain sync resumes from the checkpoint. It fires BETWEEN files — a hang
inside one file's import runs until the wall-clock hard deadline. 0
disables it. The full sync-resumability knob table lives in CLAUDE.md
("Sync resumability + lock tuning").