Skip to content

standalone: env var to disable the post-load madvise of embedded source - #37357

Merged
Jarred-Sumner merged 4 commits into
mainfrom
ciro/madvise-env-optout
Aug 11, 2026
Merged

standalone: env var to disable the post-load madvise of embedded source#37357
Jarred-Sumner merged 4 commits into
mainfrom
ciro/madvise-env-optout

Conversation

@cirospaciari

@cirospaciari cirospaciari commented Aug 11, 2026

Copy link
Copy Markdown
Member

What does this PR do?

Adds BUN_FEATURE_FLAG_DISABLE_STANDALONE_MADVISE, a runtime opt-out for the behavior introduced in #29320: a bun build --compile executable calls madvise(MADV_DONTNEED) on its embedded source section once the entrypoint has loaded, so anything that touches the source afterwards (a lazy require(), resolving a stack trace) pages it back in from the executable on disk. Deployments where that re-read is undesirable can set the flag in the environment of the running executable to keep the pages resident:

BUN_FEATURE_FLAG_DISABLE_STANDALONE_MADVISE=1 ./myapp

The compiled binary reads it at runtime, nothing is baked in at compile time. It follows the existing BUN_FEATURE_FLAG_DISABLE_* escape hatches: registered in env_var.rs, checked in hint_source_pages_dont_need() (which returns early, sharing the existing len == 0 return), falsy values such as 0 keep the default. No effect on Windows or on the plain interpreter, which never issue the hint. Like the other BUN_FEATURE_FLAG_DISABLE_* escape hatches it is not documented.

#37356 is the alternative of removing the madvise call altogether; the two are mutually exclusive.

How did you verify your code works?

test/js/bun/compile/standalone-madvise-tla.test.ts now runs the same compiled binary three times with BUN_DEBUG_StandaloneModuleGraph=1: with the variable unset (explicitly removed from the child env so an ambient value on the host cannot leak in), set to 0, and set to 1. The first two must log a hintSourcePagesDontNeed: line, the last must not log one at all, since the flag path returns before logging anything. The test stays debug-only because it relies on the scoped logger.

Against a debug build without this change the 1 case fails as expected:

Expected to not contain: "hintSourcePagesDontNeed:"
Received: "before-await\nafter-await\n[standalonemodulegraph] hintSourcePagesDontNeed: MADV_DONTNEED 4096 bytes\n"

With the change, bun bd test test/js/bun/compile/standalone-madvise-tla.test.ts passes (22 assertions).


no test proof · iteration 1 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/js/bun/compile/standalone-madvise-tla.test.ts

…mbedded source resident

Since #29320, a compiled executable calls madvise(MADV_DONTNEED) on its
embedded source section once the entrypoint has loaded, so the pages are
re-read from the executable on disk the next time something touches them
(a lazy require(), resolving a stack trace). Give deployments where that
re-read is undesirable a way out: setting the flag in the environment of
the running executable skips the hint. It is read at runtime by the
compiled binary, not at `bun build --compile` time, and follows the
existing BUN_FEATURE_FLAG_DISABLE_* escape hatches (checked inside the
implementing crate, falsy values keep the default).
@robobun

robobun commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator
Updated 6:54 PM PT - Aug 10th, 2026

@robobun, your commit bd538c1 is building: #91824

Comment thread src/bun_core/env_var.rs Outdated
@cirospaciari

Copy link
Copy Markdown
Member Author

@robobn adopt

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Adds a runtime feature flag for standalone MADV_DONTNEED behavior. The standalone module graph skips the hint when enabled. Tests cover unset, "0", and "1" values.

Changes

Standalone madvise control

Layer / File(s) Summary
Feature flag and runtime hint control
src/bun_core/env_var.rs, src/standalone_graph/StandaloneModuleGraph.rs
Adds the public boolean feature flag and skips page alignment and madvise(MADV_DONTNEED) when the flag is enabled.
Behavior validation and executable documentation
test/js/bun/compile/standalone-madvise-tla.test.ts
Runs standalone executables with unset, "0", and "1" values. It verifies the expected hint output for each value.

Possibly related PRs

  • oven-sh/bun#37000: Adds another environment-backed boolean feature flag in the same declaration file.
  • oven-sh/bun#37356: Modifies the same standalone madvise function and related test.

Suggested reviewers: robobun

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly identifies the new environment variable and the standalone post-load madvise behavior it controls.
Description check ✅ Passed The description includes both required sections and explains the change, behavior, scope, and verification steps in sufficient detail.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/bundler/executables.mdx`:
- Around line 401-412: Update the “Keeping the embedded source resident” section
to qualify the behavior as applying only on supported targets where Bun issues
the source-page hint, specifically macOS, Linux, and Android. Revise the final
platform-scope sentence so unsupported targets such as FreeBSD are not implied
to release source pages, while retaining the existing Windows and direct-script
exceptions.

In `@test/js/bun/compile/standalone-madvise-tla.test.ts`:
- Around line 48-50: Update the baseline child environment used by the
standalone madvise test to explicitly set
BUN_FEATURE_FLAG_DISABLE_STANDALONE_MADVISE to undefined when constructing
bunEnv, ensuring ambient process environment values cannot disable the feature
while preserving the existing assertions.
🪄 Autofix

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: 444c78f9-d63b-4652-bdd6-806010d8c898

📥 Commits

Reviewing files that changed from the base of the PR and between 23d233b and 580720f.

📒 Files selected for processing (4)
  • docs/bundler/executables.mdx
  • src/bun_core/env_var.rs
  • src/standalone_graph/StandaloneModuleGraph.rs
  • test/js/bun/compile/standalone-madvise-tla.test.ts

Comment thread docs/bundler/executables.mdx Outdated
Comment thread test/js/bun/compile/standalone-madvise-tla.test.ts Outdated

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — straightforward escape-hatch env var following the established BUN_FEATURE_FLAG_DISABLE_* pattern.

What was reviewed:

  • The flag check is an early return before the madvise call, inside the non-Windows cfg block; .get().unwrap_or(false) matches sibling call sites (e.g. BUN_FEATURE_FLAG_DISABLE_EPOLL_PWAIT2, BUN_FEATURE_FLAG_DISABLE_STREAMING_INSTALL).
  • new_feature_flag! with {} defaults to false, so unset/0 keep the madvise firing as documented.
  • Test covers flag=1 and flag=0 against the same compiled binary and adds a not.toContain("skipped") guard to the default case; bunEnv doesn't set this flag so the baseline path is genuinely exercised.
Extended reasoning...

Overview

Adds BUN_FEATURE_FLAG_DISABLE_STANDALONE_MADVISE, a runtime opt-out for the post-entrypoint madvise(MADV_DONTNEED) on the embedded source section of bun build --compile binaries. Four files: one new_feature_flag! line in env_var.rs, an early-return guard in StandaloneModuleGraph::hint_source_pages_dont_need(), an extension of the existing debug-only test, and a docs section in executables.mdx.

Security risks

None. The flag only skips a best-effort memory-residency hint; no user input reaches a syscall argument, no allocation, no pointer arithmetic on the new path (the return happens before the page-rounding math). The env var goes through the same cached boolean machinery every other feature flag uses.

Level of scrutiny

Low. This is the canonical escape-hatch shape used dozens of times in this file — a boolean feature flag registered via macro and checked with .get().unwrap_or(false) at the single call site. The guarded operation was already best-effort (failures are logged and ignored), so skipping it entirely is strictly a subset of existing behavior. Windows and the plain interpreter are unaffected because the check sits inside the #[cfg(not(windows))] / mac-linux-android block, after the get_data() and len == 0 guards.

Other factors

The test extension follows harness conventions: spreads bunEnv, drains stdout/stderr/exited concurrently, asserts stderr and exit code last, and reuses the compiled binary from the existing case (proving the flag is read at runtime, not build time). It asserts both the presence of the skipped log when set and its absence when 0/unset, plus the absence of the MADV_DONTNEED log when skipped. Confirmed harness.ts doesn't pre-set this flag. The docs accurately describe scope (runtime-only, no effect on Windows or bun direct).

Comment thread src/standalone_graph/StandaloneModuleGraph.rs Outdated
@robobun

robobun commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Adopted. Pushed 4840788: docs section removed, flag comment and skipped log dropped earlier in 0f26705. The test checks that no hintSourcePagesDontNeed line is logged when the flag is set (fails against a build without the flag, passes with it). All review threads addressed.

@github-actions

Copy link
Copy Markdown
Contributor

This PR may be a duplicate of:

  1. Revert "perf(standalone): madvise(MADV_DONTNEED) embedded source pages after entrypoint load" (#29320) #37356 - Targets the same madvise(MADV_DONTNEED) behavior from perf(standalone): madvise(MADV_DONTNEED) embedded source pages after entrypoint load #29320 in src/standalone_graph/StandaloneModuleGraph.rs and the same standalone-madvise-tla test, but resolves it by deleting hint_source_pages_dont_need() outright rather than gating it behind an env var — the two are mutually exclusive.

🤖 Generated with Claude Code

The flag check now returns silently alongside the len == 0 check, so the
test asserts that no hintSourcePagesDontNeed line is logged when the flag
is set. The unset baseline clears the variable explicitly so an ambient
value on the host cannot leak into it. Docs name the platforms where the
hint is issued.
Comment thread docs/bundler/executables.mdx Outdated

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — a one-line new_feature_flag! registration plus an early-return guard, following the existing BUN_FEATURE_FLAG_DISABLE_* pattern exactly.

Checked that .get().unwrap_or(false) matches sibling call sites (e.g. DISABLE_MEMFD, DISABLE_SQL_AUTO_PIPELINING, DISABLE_EPOLL_PWAIT2).
Checked that the guard sits inside the existing cfg(macos/linux/android) block, so Windows/other targets are unaffected and len == 0 still short-circuits first.
Checked the test loop: undefined in the spread bunEnv drops any ambient value, "0" stays falsy per string_is_truthy, and the negative assertion for "1" is the only new branch.
All of cirospaciari's review threads (comment, log line, docs) are resolved in the current diff.

Extended reasoning...

Overview

Three files: a single new_feature_flag!(BUN_FEATURE_FLAG_DISABLE_STANDALONE_MADVISE, ...) line in src/bun_core/env_var.rs (inserted alphabetically among the other DISABLE_* flags), a guard added to the existing len == 0 early return in StandaloneModuleGraph::hint_source_pages_dont_need(), and an extension of the existing debug-only standalone-madvise-tla.test.ts to loop over three env values. A doc-comment sentence on the function was also updated.

Security risks

None. The flag only skips an advisory madvise(MADV_DONTNEED) hint that is already best-effort. No new syscalls, no untrusted input parsing, no allocation, no pointer arithmetic reached by the new branch. Reading an env var via the cached feature_flag accessor is the same mechanism used by dozens of other escape hatches.

Level of scrutiny

Low. This is a mechanical escape-hatch addition that mirrors an established pattern byte-for-byte — I grepped and confirmed .get().unwrap_or(false) is exactly how BUN_FEATURE_FLAG_DISABLE_MEMFD, _SQL_AUTO_PIPELINING, and _EPOLL_PWAIT2 are checked. The guard is placed inside the existing #[cfg(any(macos, linux, android))] block after len is computed, so it cannot affect the Windows path or the get_data() bailouts, and len == 0 still short-circuits before the env lookup. new_feature_flag! with {} defaults to false, so unwrap_or(false) is redundant-but-consistent with neighbors.

Other factors

The maintainer (cirospaciari) actively drove this PR via inline feedback (drop the flag comment, drop the skipped log, drop the docs section) and every thread is resolved in the current diff — the env_var.rs line has no comment, there is no separate "skipped" log, and docs/bundler/executables.mdx is no longer in the changed-files list. The test follows harness conventions: {...bunEnv, KEY: undefined} to strip an ambient value, pipes drained concurrently via Promise.all, exit code asserted last, test.skipIf(isWindows || !isDebug) preserved. The "0" case correctly asserts the hint still fires because string_is_truthy treats "0" as falsy. The PR description documents that the "1" case fails against a build without the change, satisfying the fails-for-the-right-reason bar. #37356 is noted as the mutually-exclusive alternative, which is a maintainer decision outside this review's scope.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — a one-line new_feature_flag! registration plus an early-return guard, following the existing BUN_FEATURE_FLAG_DISABLE_* pattern verbatim.

What was reviewed:

  • .get().unwrap_or(false) matches the call shape used by every other BUN_FEATURE_FLAG_DISABLE_* consumer (dns.rs, PostgresSQLConnection.rs, analytics/lib.rs, etc.).
  • The guard sits before both scoped_log! sites in hint_source_pages_dont_need(), so the test's not.toContain("hintSourcePagesDontNeed:") assertion for the "1" case cannot pass vacuously.
  • Test loop covers unset/"0"/"1", spreads bunEnv with the flag key set to undefined for the baseline (hermetic), drains stdout/stderr/exited concurrently, and asserts exitCode last.
Extended reasoning...

Overview

Three files: (1) src/bun_core/env_var.rs gains one new_feature_flag! line inserted alphabetically among ~30 siblings; (2) src/standalone_graph/StandaloneModuleGraph.rs OR's the flag check into the existing len == 0 early return inside the macOS/Linux/Android #[cfg] block of hint_source_pages_dont_need(), and extends the doc comment by one clause; (3) the existing debug-only test standalone-madvise-tla.test.ts is extended to run the compiled binary under three flag values instead of one.

Security risks

None. The flag only controls whether an advisory madvise(MADV_DONTNEED) is issued on file-backed pages of the executable's own embedded source. Skipping it keeps pages resident (slightly higher RSS); issuing it lets the kernel reclaim them. No user input reaches a syscall argument, no allocation, no privilege boundary.

Level of scrutiny

Low. This is a mechanical escape-hatch addition matching a heavily-precedented pattern in the same file. The consumption site (.get().unwrap_or(false)) is byte-identical to how BUN_FEATURE_FLAG_DISABLE_SQL_AUTO_PIPELINING, BUN_FEATURE_FLAG_DISABLE_ADDRCONFIG, BUN_FEATURE_FLAG_DISABLE_EPOLL_PWAIT2, etc. are read elsewhere. The only behavioral change when the flag is unset is none — the added disjunct is || false.

Other factors

A maintainer (cirospaciari) has been actively driving this PR: adopted it, requested the flag comment be dropped, the skipped-log line be dropped, and the docs section be removed — all done and all threads resolved. The test follows harness conventions (await using, Promise.all pipe drain, ...bunEnv spread with undefined to strip ambient values, exitCode asserted last) and the PR description records that the "1" case fails against a build without the change. The competing #37356 (delete the madvise outright) is a direction call for maintainers, not a correctness concern for this PR.

@Jarred-Sumner
Jarred-Sumner merged commit 9fcdea8 into main Aug 11, 2026
49 of 51 checks passed
@Jarred-Sumner
Jarred-Sumner deleted the ciro/madvise-env-optout branch August 11, 2026 02:09
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.

3 participants