Add Bun.file().lock() and FileLock

Closed
RO
2mo ago
+1098-2

Adds advisory file locking to Bun.file().

API

await using lock = await Bun.file("data.json").lock();
const data = JSON.parse(await lock.text());
data.count++;
await lock.truncate();
await lock.write(JSON.stringify(data));
// lock released on scope exit
  • Bun.file().lock({ exclusive = true, nonblocking = false, signal? }) returns Promise<FileLock>. On POSIX this is flock(2); on Windows, LockFileEx over the full file range. The wait runs on the WorkPool and polls LOCK_NB with a futex-backed sleep so an AbortSignal actually breaks out of the wait rather than resolving late.
  • FileLock implements Symbol.asyncDispose and unlock()/close(). While held it also exposes bytes(n?), read(n?), text(n?), arrayBuffer(n?), write(data), and truncate(n?) operating on the locked fd (positional from offset 0).
  • Bun.file(fd).unlock() releases a lock on a caller-owned fd. On a path-backed BunFile it rejects, pointing the user at the FileLock returned by lock().

Implementation

  • New bun_sys::flock/funlock wrappers (POSIX flock, Win32 LockFileEx/UnlockFileEx) and Tag::flock.
  • FileLock JS class (src/runtime/webcore/FileLock.classes.ts, src/runtime/webcore/blob/FileLock.rs).
  • Two ConcurrentPromiseTask contexts: LockTaskCtx for acquisition and IoTaskCtx for read/write/truncate.

Tests

test/js/bun/io/bun-file-lock.test.ts covers: async disposable release, shared locks, cross-process blocking wait, nonblocking rejection under contention, AbortSignal mid-wait and pre-aborted, fd-backed lock/unlock, error paths for non-file blobs and path-backed unlock(), and the full read/write/truncate round-trip. All 16 tests pass on the debug build and fail on the released binary (lock is not a function).


no test proof · iteration 4 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/js/bun/io/bun-file-lock.test.ts

Fixes #26230