Skip to content

Make advisory file locks kernel-owned and portable across hosts#961

Open
brandonpayton wants to merge 1 commit into
mainfrom
emdash/flexible-lock-table-size-vmn6v
Open

Make advisory file locks kernel-owned and portable across hosts#961
brandonpayton wants to merge 1 commit into
mainfrom
emdash/flexible-lock-table-size-vmn6v

Conversation

@brandonpayton

Copy link
Copy Markdown
Member

Why

Kandelo currently stores advisory file locks—the cooperative byte-range locks used by databases and other multi-process programs—in a fixed TypeScript SharedArrayBuffer table. That makes JavaScript, rather than the kernel, responsible for lock ownership, range replacement, and capacity errors. It can misidentify the same open file after a hard link, rename, or unlink, and a native WebAssembly host would have to reimplement the JavaScript lock service.

This affects guest programs that coordinate file access across processes, especially SQLite-style workloads, and it lets Node and browser hosts diverge on behavior that should be one Portable Operating System Interface (POSIX) kernel contract. Moving the authoritative state into Rust gives every host the same file identity, ownership, lifecycle, range, blocking, and exhaustion rules while keeping failures truthful and bounded.

What changed

  • Add one machine-wide AdvisoryLockManager to ProcessTable. It starts empty and uses a geometrically growing, non-shrinking Vec<LockRecord> sorted by file identity and range, with binary file selection and a hard limit of 4096 normalized records.
  • Implement conflict-first, two-pass range mutation in Rust: positive/zero/negative-length normalization, read compatibility, deterministic blockers, same-owner replacement, split/coalesce, upgrade/downgrade, partial unlock, and atomic ENOLCK failure.
  • Give every open file description a kernel-global OfdId, preserve it through dup/fork/exec/SCM_RIGHTS, and release OFD/flock() locks only on the true machine-wide final reference. Process-lock close, exec, exit, signal-exit, and crash cleanup remain PID/FileId based.
  • Derive host file identity from exact live-handle fstat values. Node uses bigint-native stat data, VFS device IDs are backend-qualified, and OPFS assigns session inode tokens unified with isSameEntry() and retained through rename/unlink.
  • Publish Rust advisory-lock wake events through the generic wake stream. The host only parks and reschedules conflicting F_SETLKW channels; it does not store or inspect lock state, and ENOLCK never enters the retry path.
  • Remove host_fcntl_lock, SharedLockTable, its runtime tests/export/registration, and crash spinlock-reset cleanup. Add Rust manager/lifecycle tests plus real kernel-Wasm Node and Chromium/OPFS coverage, targeted benchmark workloads, and contract documentation.

ABI and public API impact

  • Bump kernel ABI 39 → 40 and fork/exec state 11 → 12.
  • Add guest-visible ENOLCK (37) and distinguish it from lock conflicts (EAGAIN).
  • Remove the required host_fcntl_lock import and public host-package SharedLockTable, LockInfo, and WasmPosixKernel.registerSharedLockTable() APIs without a compatibility shim.
  • Widen host StatResult.dev/ino to number | bigint; native backends should provide exact bigint values.
  • ABI 39 kernels, hosts, libc, guests, packages, and VFS images cannot be mixed with ABI 40 artifacts. The kernel, guest/benchmark programs, host bundles, rootfs, and rootfs packages were rebuilt locally through the normal source/fallback path; no external artifacts were published.

Validation

Command / coverage Result
bash scripts/dev-shell.sh bash scripts/check-abi-version.sh Passed after rebase: ABI snapshot, generated TypeScript bindings, and libc constants agree on ABI 40.
bash scripts/dev-shell.sh bash build.sh Passed after rebase. The unavailable ABI 40 binary index failed loudly, then the resolver rebuilt the rootfs packages from source and produced a 336-path manifest plus a 16,787,688-byte VFS image.
bash scripts/dev-shell.sh bash scripts/ci-run-test-suite.sh cargo-kernel 1169/1169 passed after rebase.
bash scripts/dev-shell.sh bash scripts/ci-run-test-suite.sh vitest 149 files and 1317 tests passed; 2 expected failures and 130 skips. One unchanged test added by rebased main exceeded its 5-second timeout. The focused retry below passed.
bash scripts/dev-shell.sh bash -lc 'cd host && npx vitest run ../tests/package-system/wasm-artifact-guards.test.ts' 16/16 passed, including the previously timed-out primary/fallback ABI extraction case.
bash scripts/dev-shell.sh bash -lc 'export KANDELO_PLAYWRIGHT_PORT=55401; cd apps/browser-demos; npx playwright test test/opfs-advisory-lock.spec.ts test/opfs-identity.spec.ts --project=chromium' 2/2 passed after rebase. The isolated port avoids reusing a Vite server from another worktree.
bash scripts/dev-shell.sh bash scripts/ci-run-test-suite.sh libc 303 passed, 0 failed, 20 expected failures, 1 flaky.
bash scripts/dev-shell.sh bash scripts/ci-run-test-suite.sh posix 174 passed, 0 failed, 3 expected failures, 2 skipped.
bash scripts/dev-shell.sh bash scripts/ci-run-test-suite.sh sortix 5033 passed, 0 failed, 23 expected failures, 53 skipped.
Focused Node advisory-lock/kernel-Wasm coverage 35/35 passed.
SQLite manydb.test, Node and Chromium Node: 901 cases, 0 errors. Browser: 154 cases, 0 errors.
git diff --check and git diff origin/main...HEAD --check Passed.

Targeted current-branch advisory-lock measurements were run for three rounds with:

  • npx tsx benchmarks/run.ts --suite=syscall-io --rounds=3
  • npx tsx benchmarks/run.ts --host=browser --suite=syscall-io --rounds=3

Node medians were 14.46/11.63/14.10/11.83 µs per operation for many-file acquire/conflict/replace/unlock and 11.97/9.27/13.24/8.94 µs for dense-file operations. Browser medians were 15.75/7.87/15.75/15.75 µs and 11.72/11.72/11.72/11.72 µs respectively. There is no ABI 39 baseline, so this PR makes no performance-improvement or no-regression claim.

Not completed

  • The full browser asset gate reports 25 missing optional package artifacts, so the broad browser suite and manual ./run.sh browser verification were not completed. The focused Chromium/OPFS tests above did run against the rebuilt ABI 40 kernel.
  • A full before/after Node and browser benchmark comparison was not possible because there is no compatible ABI 39 baseline artifact for these new metrics.
  • External packages/artifacts were not published.
  • Repository-wide cargo fmt --check still reports broad pre-existing formatting drift; changed files were reviewed and git diff --check is clean.

Remaining documented gaps

  • Blocking advisory locks do not yet perform deadlock detection.
  • Ordinary host-file OFD metadata is still process-local outside the new global OfdId lock identity.
  • close() EINTR handling and the Linux close_range() extension remain incomplete.
  • A backend that cannot prove a stable exact file identity fails locking with ENOLCK; it never falls back to a pathname hash.

Move machine-wide advisory lock state, POSIX range semantics, ownership, and bounded capacity into ProcessTable using a file-sorted high-water Vec. Add stable FileId and OfdId lifetimes, exact host file identity, Rust wake events, and remove the SharedLockTable host path.

Bump ABI 39 to 40 and fork state 11 to 12, expose ENOLCK, and update generated bindings, integration coverage, benchmarks, and platform documentation.
@github-actions

Copy link
Copy Markdown

Phase B-1 matrix build status — pr-961-staging

ABI v40. 71 built, 0 failed, 71 total.

Package Arch Status Sha
icu wasm32 built 6073f6b9
libcurl wasm32 built 8ba4d3cb
libcxx wasm32 built e9099a6a
libcxx wasm64 built 6595ad36
libiconv wasm32 built d8e5251f
libpng wasm32 built 8bea219e
libxml2 wasm32 built 56a278e0
libzip wasm32 built d5f68040
openssl wasm32 built b2bef84f
openssl wasm64 built 6e6fdce4
sqlite wasm32 built a761b76d
sqlite wasm64 built bd1cbd6e
zlib wasm32 built 8dce6292
zlib wasm64 built 867c02ca
bc wasm32 built 2871d1de
bzip2 wasm32 built 350db8d7
coreutils wasm32 built 973d4a19
curl wasm32 built b69c824b
dash wasm32 built 6943b388
diffutils wasm32 built c71e703a
dinit wasm32 built 673211df
fbdoom wasm32 built c5f9f084
file wasm32 built dc3390ee
findutils wasm32 built c8a3a4d5
gawk wasm32 built 61f5bfdf
git wasm32 built 16269c4c
grep wasm32 built be6909b2
gzip wasm32 built 05ef3c19
hello wasm32 built ffa8c082
kandelo-sdk wasm32 built 4634c890
kernel wasm32 built 0826b047
less wasm32 built 2ed18735
lsof wasm32 built ec360d50
m4 wasm32 built b0233bb6
make wasm32 built 81f6e5ae
mariadb wasm32 built 64c0be1a
mariadb wasm64 built fdddd569
modeset wasm32 built 3d213441
msmtpd wasm32 built 08a878d8
nano wasm32 built b7d0c527
ncurses wasm32 built 33640b6b
netcat wasm32 built 7074e3fb
nginx wasm32 built b5e91b44
php wasm32 built 45ced3b0
posix-utils-lite wasm32 built d1809280
ruby wasm32 built d58eeef7
sed wasm32 built dd96947f
spidermonkey wasm32 built 4c6728bb
tar wasm32 built 32097203
tcl wasm32 built b11a80f5
unzip wasm32 built cffbfa3f
userspace wasm32 built 331a1dda
vim wasm32 built efbacd81
wget wasm32 built b09ddfee
xz wasm32 built 1f72f887
zip wasm32 built e8461b64
zstd wasm32 built a0da68ae
bash wasm32 built 23aa8595
mariadb-test wasm32 built 79a5e42d
mariadb-vfs wasm32 built 505ba75c
mariadb-vfs wasm64 built 142d55c8
nethack wasm32 built b5ecf4cc
node wasm32 built e6710e9b
spidermonkey-node wasm32 built 3a1bdc78
vim-browser-bundle wasm32 built 8695e8fd
nethack-browser-bundle wasm32 built 4d417811
rootfs wasm32 built 34e50c21
shell wasm32 built 9421bed8
lamp wasm32 built e6b7d061
node-vfs wasm32 built 3ee3f064
wordpress wasm32 built eb967d1d

Auto-generated; replaced on each push. Raw data in the publish-status workflow artifact.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant