sys: cache memfd_create ENOSYS + BUN_FEATURE_FLAG_DISABLE_MEMFD - #29465
Conversation
memfd_create requires kernel ≥ 3.17. All existing callers (Blob, spawn stdio, process IPC) already fall back to heap/pipe/socketpair on error, so kernel 3.10 (RHEL 7) works today — but every call retries the failing syscall. - Cache ENOSYS in bun.sys.memfd_create so subsequent calls fail fast - Add BUN_FEATURE_FLAG_DISABLE_MEMFD to force the fallback path (useful under seccomp, and for testing) - Test the fallback paths with the flag set - docs: replace incorrect "minimum kernel 5.1" (nothing requires 5.1; io_uring detection has zero callers) with the actual floor Complements #29461 (glibc 2.17 floor).
Instead of returning a synthetic ENOSYS from inside the wrapper, expose canUseMemfd() (atomic tristate + env-flag check) and have the three callers consult it before attempting the syscall — same shape as canUseCopyFileRangeSyscall() and RWFFlagSupport.isMaybeSupported(). The wrapper still latches ENOSYS to -1 on first real failure.
WalkthroughAdded graceful degradation for memfd usage: a feature-flag, a cached atomic latch for memfd unavailability, runtime guards that avoid memfd paths when unavailable, docs clarifying kernel support, and Linux-only tests exercising the fallback behavior. Changes
🚥 Pre-merge checks | ✅ 2✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/installation.mdx`:
- Line 24: Remove the stray non-printable control character immediately
following the bold token "**Linux users**" in the installation note; replace it
with a normal punctuation mark such as an em-dash (—) or colon (:) and ensure
there are no remaining invisible characters so the line reads e.g. "**Linux
users** — The `unzip` package..." in docs/installation.mdx.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 37bce8f2-9485-41a1-94c1-fc610192f49c
📒 Files selected for processing (4)
docs/installation.mdxsrc/env_var.zigsrc/sys.zigtest/js/bun/memfd-disabled.test.ts
| </CodeGroup> | ||
| <Note> | ||
| **Linux users** The `unzip` package is required to install Bun. Use `sudo apt install unzip` to install the unzip package. Kernel version 5.6 or higher is strongly recommended, but the minimum is 5.1. Use `uname -r` to check Kernel version. | ||
| **Linux users** The `unzip` package is required to install Bun. Use `sudo apt install unzip` to install the unzip package. Kernel version 5.6 or higher is recommended; Bun runs on kernels as old as 3.10 (RHEL 7) with graceful degradation of newer syscalls. Use `uname -r` to check your kernel version. |
There was a problem hiding this comment.
Remove the accidental control character in the Linux note.
There’s a non-printable character after **Linux users** that should be replaced with normal punctuation (e.g., — or :) to avoid rendering/copy issues.
✏️ Proposed fix
- **Linux users** � The `unzip` package is required to install Bun. Use `sudo apt install unzip` to install the unzip package. Kernel version 5.6 or higher is recommended; Bun runs on kernels as old as 3.10 (RHEL 7) with graceful degradation of newer syscalls. Use `uname -r` to check your kernel version.
+ **Linux users** — The `unzip` package is required to install Bun. Use `sudo apt install unzip` to install the unzip package. Kernel version 5.6 or higher is recommended; Bun runs on kernels as old as 3.10 (RHEL 7) with graceful degradation of newer syscalls. Use `uname -r` to check your kernel version.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| **Linux users** The `unzip` package is required to install Bun. Use `sudo apt install unzip` to install the unzip package. Kernel version 5.6 or higher is recommended; Bun runs on kernels as old as 3.10 (RHEL 7) with graceful degradation of newer syscalls. Use `uname -r` to check your kernel version. | |
| **Linux users** — The `unzip` package is required to install Bun. Use `sudo apt install unzip` to install the unzip package. Kernel version 5.6 or higher is recommended; Bun runs on kernels as old as 3.10 (RHEL 7) with graceful degradation of newer syscalls. Use `uname -r` to check your kernel version. |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/installation.mdx` at line 24, Remove the stray non-printable control
character immediately following the bold token "**Linux users**" in the
installation note; replace it with a normal punctuation mark such as an em-dash
(—) or colon (:) and ensure there are no remaining invisible characters so the
line reads e.g. "**Linux users** — The `unzip` package..." in
docs/installation.mdx.
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/sys.zig (1)
3134-3156:⚠️ Potential issue | 🟠 MajorShort-circuit
memfd_create()once ENOSYS is latched.Right now the latch is only written. Repeated direct calls to
bun.sys.memfd_create()will still perform the failing syscall instead of returning the cached fallback signal immediately.🔧 Proposed fix
pub fn memfd_create(name: [:0]const u8, flags_: MemfdFlags) Maybe(bun.FD) { if (comptime !Environment.isLinux) `@compileError`("linux only!"); + if (memfd_enosys.load(.monotonic)) { + return .{ .err = Error.fromCode(.NOSYS, .memfd_create) }; + } var flags: u32 = `@intFromEnum`(flags_); while (true) { const rc = std.os.linux.memfd_create(name, flags);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/sys.zig` around lines 3134 - 3156, The memfd_create function currently only sets the memfd_enosys latch on .NOSYS but continues to attempt the syscall on subsequent calls; add an early check at the start of pub fn memfd_create(name, flags_: MemfdFlags) to short‑circuit when memfd_enosys is set (use memfd_enosys.load(.monotonic)) and immediately return a Maybe(bun.FD) error representing ENOSYS instead of invoking std.os.linux.memfd_create; place this check before the while loop and use the same error representation path as Maybe(bun.FD).errnoSys would produce for .NOSYS so callers get the cached fallback behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@src/sys.zig`:
- Around line 3134-3156: The memfd_create function currently only sets the
memfd_enosys latch on .NOSYS but continues to attempt the syscall on subsequent
calls; add an early check at the start of pub fn memfd_create(name, flags_:
MemfdFlags) to short‑circuit when memfd_enosys is set (use
memfd_enosys.load(.monotonic)) and immediately return a Maybe(bun.FD) error
representing ENOSYS instead of invoking std.os.linux.memfd_create; place this
check before the while loop and use the same error representation path as
Maybe(bun.FD).errnoSys would produce for .NOSYS so callers get the cached
fallback behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: cc77957b-1a90-44a0-94b6-534a0a65b2a0
📒 Files selected for processing (4)
src/allocators/LinuxMemFdAllocator.zigsrc/bun.js/api/bun/process.zigsrc/bun.js/api/bun/spawn/stdio.zigsrc/sys.zig
There was a problem hiding this comment.
LGTM — straightforward ENOSYS caching with a follow-on feature flag, matching established patterns.
Extended reasoning...
Overview
This PR touches 4 files: a docs fix, a new feature flag entry in env_var.zig, the core caching logic in sys.zig, and a new test file. The scope is narrow and self-contained.
Security risks
None. The change only adds a short-circuit in memfd_create that returns ENOSYS earlier (when already known unsupported or explicitly disabled). No auth, crypto, or permission-sensitive code is involved.
Level of scrutiny
Low-to-medium. The logic in sys.zig is a small write-once atomic flag (false → true, never reversed), so monotonic ordering is sound — the worst-case race is two threads each attempt the syscall once before the cached value propagates. The feature flag follows the identical pattern of other BUN_FEATURE_FLAG_DISABLE_* flags. The doc correction is accurate per the PR description (io_uring check had zero callers).
Other factors
No bugs were found by the automated system. All callers already fall back on error (Blob → heap, spawn stdio → pipe, IPC → socketpair), so this change purely optimizes the retry path without altering behavior on kernels that support memfd_create. Tests cover both the Blob and spawn-stdin paths under the disabled flag.
## Summary - The Blob-stdin test inlined a 64 KiB payload **twice** into the `-e` script via `JSON.stringify`, yielding a 131,394-byte argv entry — over Linux's `MAX_ARG_STRLEN` (32 × PAGE_SIZE = 128 KiB) — so `posix_spawn` failed with `E2BIG`. Now the payload is generated inside the child; `canUseMemfd` has no size gate for in-memory Blobs so the same code path is exercised. - Both tests asserted `stderr === ""`, which fails on ASAN debug builds because JSC prints `WARNING: ASAN interferes with JSC signal handlers…`. Added a `stripAsanWarning` filter (same approach as `broadcast-channel-worker-gc.test.ts`, `fetch-abort-queued.test.ts`, etc). Follow-up to #29465. ## Test plan - [x] Reproduced original failure on Linux: `E2BIG: argument list too long, posix_spawn` - [x] Fixed test passes on Linux ASAN debug build (2 pass / 0 fail) - [ ] CI: linux test suite green --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
## Summary - The Blob-stdin test inlined a 64 KiB payload **twice** into the `-e` script via `JSON.stringify`, yielding a 131,394-byte argv entry — over Linux's `MAX_ARG_STRLEN` (32 × PAGE_SIZE = 128 KiB) — so `posix_spawn` failed with `E2BIG`. Now the payload is generated inside the child; `canUseMemfd` has no size gate for in-memory Blobs so the same code path is exercised. - Both tests asserted `stderr === ""`, which fails on ASAN debug builds because JSC prints `WARNING: ASAN interferes with JSC signal handlers…`. Added a `stripAsanWarning` filter (same approach as `broadcast-channel-worker-gc.test.ts`, `fetch-abort-queued.test.ts`, etc). Follow-up to #29465. ## Test plan - [x] Reproduced original failure on Linux: `E2BIG: argument list too long, posix_spawn` - [x] Fixed test passes on Linux ASAN debug build (2 pass / 0 fail) - [ ] CI: linux test suite green --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
…-sh#29465) ## What `memfd_create` requires kernel ≥ 3.17. A binary-level syscall audit (in oven-sh#29461) found that every Bun caller already falls back on error — Blob → heap, spawn stdio → pipe, process IPC → socketpair — so kernel 3.10 (RHEL 7) works today, but every call retries the failing syscall. This PR: - Caches ENOSYS in `bun.sys.memfd_create` so subsequent calls return immediately - Adds `BUN_FEATURE_FLAG_DISABLE_MEMFD` to force the fallback (seccomp environments, testing) - Tests that Blob and spawn-stdin work with the flag set - Fixes `docs/installation.mdx`: "minimum kernel 5.1" was never true (the io_uring check it referenced has zero callers). Actual floor is ~3.10 with degraded atomicity. Complements oven-sh#29461 (glibc 2.17). ## Test plan - [ ] CI: linux test suite passes with new `memfd-disabled.test.ts` - [ ] CI: zig check on all platforms
## Summary - The Blob-stdin test inlined a 64 KiB payload **twice** into the `-e` script via `JSON.stringify`, yielding a 131,394-byte argv entry — over Linux's `MAX_ARG_STRLEN` (32 × PAGE_SIZE = 128 KiB) — so `posix_spawn` failed with `E2BIG`. Now the payload is generated inside the child; `canUseMemfd` has no size gate for in-memory Blobs so the same code path is exercised. - Both tests asserted `stderr === ""`, which fails on ASAN debug builds because JSC prints `WARNING: ASAN interferes with JSC signal handlers…`. Added a `stripAsanWarning` filter (same approach as `broadcast-channel-worker-gc.test.ts`, `fetch-abort-queued.test.ts`, etc). Follow-up to oven-sh#29465. ## Test plan - [x] Reproduced original failure on Linux: `E2BIG: argument list too long, posix_spawn` - [x] Fixed test passes on Linux ASAN debug build (2 pass / 0 fail) - [ ] CI: linux test suite green --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
…-sh#29465) ## What `memfd_create` requires kernel ≥ 3.17. A binary-level syscall audit (in oven-sh#29461) found that every Bun caller already falls back on error — Blob → heap, spawn stdio → pipe, process IPC → socketpair — so kernel 3.10 (RHEL 7) works today, but every call retries the failing syscall. This PR: - Caches ENOSYS in `bun.sys.memfd_create` so subsequent calls return immediately - Adds `BUN_FEATURE_FLAG_DISABLE_MEMFD` to force the fallback (seccomp environments, testing) - Tests that Blob and spawn-stdin work with the flag set - Fixes `docs/installation.mdx`: "minimum kernel 5.1" was never true (the io_uring check it referenced has zero callers). Actual floor is ~3.10 with degraded atomicity. Complements oven-sh#29461 (glibc 2.17). ## Test plan - [ ] CI: linux test suite passes with new `memfd-disabled.test.ts` - [ ] CI: zig check on all platforms
## Summary - The Blob-stdin test inlined a 64 KiB payload **twice** into the `-e` script via `JSON.stringify`, yielding a 131,394-byte argv entry — over Linux's `MAX_ARG_STRLEN` (32 × PAGE_SIZE = 128 KiB) — so `posix_spawn` failed with `E2BIG`. Now the payload is generated inside the child; `canUseMemfd` has no size gate for in-memory Blobs so the same code path is exercised. - Both tests asserted `stderr === ""`, which fails on ASAN debug builds because JSC prints `WARNING: ASAN interferes with JSC signal handlers…`. Added a `stripAsanWarning` filter (same approach as `broadcast-channel-worker-gc.test.ts`, `fetch-abort-queued.test.ts`, etc). Follow-up to oven-sh#29465. ## Test plan - [x] Reproduced original failure on Linux: `E2BIG: argument list too long, posix_spawn` - [x] Fixed test passes on Linux ASAN debug build (2 pass / 0 fail) - [ ] CI: linux test suite green --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
What
memfd_createrequires kernel ≥ 3.17. A binary-level syscall audit (in #29461) found that every Bun caller already falls back on error — Blob → heap, spawn stdio → pipe, process IPC → socketpair — so kernel 3.10 (RHEL 7) works today, but every call retries the failing syscall.This PR:
bun.sys.memfd_createso subsequent calls return immediatelyBUN_FEATURE_FLAG_DISABLE_MEMFDto force the fallback (seccomp environments, testing)docs/installation.mdx: "minimum kernel 5.1" was never true (the io_uring check it referenced has zero callers). Actual floor is ~3.10 with degraded atomicity.Complements #29461 (glibc 2.17).
Test plan
memfd-disabled.test.ts