Skip to content

[P1][kernel] Exclusive open lock to prevent double-writer divergence #21

Description

@cevheri

Summary

Two concurrent open({ path }) calls on the same file produce independent in-memory stores that append to the same WAL without coordination. Updates are lost, state diverges, and the log can be corrupted. No error is raised.

Audit ID: B4 (High) — pre-announcement blocker
Comparable DBs: SQLite, LMDB, LevelDB all enforce exclusive access.

Impact

Everyday in the stated beachhead (test/dev):

  • Parallel test workers on one fixture file
  • Stale dev-server process + CLI write
  • Hot-reload leaving old handle open

Lock machinery already exists for the CLI (src/cli/lock.ts) but the kernel does not use it.

Root cause

const opened = openLog(options.path, options.fs);
committed = opened.entries;
log = opened.log;

No exclusive lock, no in-process deduplication of open paths.

Proposed fix

1. FileSystem seam extension (minimal)

Add optional lock support or a dedicated helper on FileSystem:

  • lock(path): void — exclusive create <path>.lock with O_EXCL / wx semantics
  • unlock(path): void — release on close()

Reuse sentinel pattern from src/cli/lock.ts for consistency.

2. In-process guard

Module-level Set<string> of currently open paths (same process, two open() calls) → throw immediately without touching disk.

3. Kernel open() flow

  1. In-process check
  2. Acquire <path>.lock via fs before openLog
  3. Release lock in close() (always, idempotent close)

4. Browser / OPFS

Document that OPFS single-handle assumption remains; lock is no-op or in-process only when no cross-tab locking exists.

Tests

  • Two open() same path same process → second throws.
  • close() then reopen → succeeds.
  • Two processes: use temp dir + fork/spawn test if feasible; else document CLI-only E2E and unit-test the lock file creation.

Related issues

Acceptance criteria

  • Second open() on same path in one process fails loudly.
  • Cross-process second writer fails (Node adapter).
  • Lock released on close().
  • bun run gate green.
  • Changeset required (new error cases / possibly FileSystem interface extension).

Agent constraints

  • Widening FileSystem affects node-fs, opfs, sim/simfs — update all adapters.
  • Keep kernel logic in core.ts minimal; lock acquisition can live in adapter or thin wrapper in index.ts.

Key files

  • src/core.ts, src/adapter/node-fs.ts, src/adapter/opfs.ts, src/sim/simfs.ts
  • src/cli/lock.ts — align or share sentinel constant
  • src/index.ts — wire default fs

Verification

bun run gate

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/kernelPre-announcement auditbugSomething isn't workingpre-announcementPre-announcement auditpriority/highPre-announcement audit

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions