Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/bundler/executables.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,18 @@ BUN_OPTIONS="--heap-prof-md" ./myapp
BUN_OPTIONS="--smol --cpu-prof-md" ./myapp
```

### Keeping the embedded source resident
Comment thread
robobun marked this conversation as resolved.
Outdated

On Linux and macOS, once a standalone executable has loaded its entrypoint, Bun tells the kernel (via `madvise`) that the pages holding the embedded JavaScript source are no longer needed, which lowers the executable's resident memory. The pages are file-backed, so anything that reads the source later (a lazy `require()`, or resolving a stack trace) transparently pages it back in from the executable on disk.

If that re-read is a problem for your deployment (for example, the executable runs from slow or removable storage), set `BUN_FEATURE_FLAG_DISABLE_STANDALONE_MADVISE` when running the executable to keep the source pages resident:

```bash terminal icon="terminal"
BUN_FEATURE_FLAG_DISABLE_STANDALONE_MADVISE=1 ./myapp
```

This is a runtime setting read by the compiled executable, so it does not need to be set when running `bun build --compile`. It has no effect on other platforms, or when running scripts with `bun` directly, because the source pages are never released there in the first place.

---

## Automatic config loading
Expand Down
1 change: 1 addition & 0 deletions src/bun_core/env_var.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ pub mod feature_flag {
new_feature_flag!(pub BUN_FEATURE_FLAG_DISABLE_SOURCE_MAPS, "BUN_FEATURE_FLAG_DISABLE_SOURCE_MAPS", {});
new_feature_flag!(pub BUN_FEATURE_FLAG_DISABLE_SPAWNSYNC_FAST_PATH, "BUN_FEATURE_FLAG_DISABLE_SPAWNSYNC_FAST_PATH", {});
new_feature_flag!(pub BUN_FEATURE_FLAG_DISABLE_SQL_AUTO_PIPELINING, "BUN_FEATURE_FLAG_DISABLE_SQL_AUTO_PIPELINING", {});
new_feature_flag!(pub BUN_FEATURE_FLAG_DISABLE_STANDALONE_MADVISE, "BUN_FEATURE_FLAG_DISABLE_STANDALONE_MADVISE", {});
new_feature_flag!(pub BUN_DISABLE_TRANSPILED_SOURCE_CODE_PREVIEW, "BUN_DISABLE_TRANSPILED_SOURCE_CODE_PREVIEW", {});
new_feature_flag!(pub BUN_FEATURE_FLAG_DISABLE_UV_FS_COPYFILE, "BUN_FEATURE_FLAG_DISABLE_UV_FS_COPYFILE", {});
new_feature_flag!(pub BUN_DUMP_STATE_ON_CRASH, "BUN_DUMP_STATE_ON_CRASH", {});
Expand Down
9 changes: 7 additions & 2 deletions src/standalone_graph/StandaloneModuleGraph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2193,7 +2193,8 @@ impl StandaloneModuleGraph {
/// The pages are clean file-backed COW, so any later read (lazy require,
/// stack-trace source lookup) faults back in transparently from the
/// executable on disk. Only applies when running as a compiled
/// standalone binary.
/// standalone binary; `BUN_FEATURE_FLAG_DISABLE_STANDALONE_MADVISE=1`
/// skips the hint.
pub fn hint_source_pages_dont_need() {
#[cfg(windows)]
{
Expand Down Expand Up @@ -2225,7 +2226,11 @@ impl StandaloneModuleGraph {

#[cfg(any(target_os = "macos", target_os = "linux", target_os = "android"))]
{
if len == 0 {
if len == 0
|| bun_core::env_var::feature_flag::BUN_FEATURE_FLAG_DISABLE_STANDALONE_MADVISE
.get()
.unwrap_or(false)
{
return;
}

Expand Down
24 changes: 21 additions & 3 deletions test/js/bun/compile/standalone-madvise-tla.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// entrypoint has top-level await. loadEntryPoint() returns a promise without
// blocking, so the call site at bun.js.zig:466 is hit synchronously before the
// main event loop spins — TLA resolution happens later in that loop.
// BUN_FEATURE_FLAG_DISABLE_STANDALONE_MADVISE, read by the compiled binary at
// runtime, skips the hint.
import { expect, test } from "bun:test";
import { bunEnv, bunExe, isDebug, isWindows, tempDir } from "harness";
import path from "node:path";
Expand Down Expand Up @@ -29,10 +31,22 @@ test.skipIf(isWindows || !isDebug)(
expect(build.stderr.toString()).not.toContain("error:");
expect(build.exitCode).toBe(0);

{
// BUN_FEATURE_FLAG_DISABLE_STANDALONE_MADVISE is read by the compiled
// executable at runtime (the binary above was built without it); a falsy
// value leaves the hint enabled. With the flag set, the function returns
// before logging anything, so the only evidence is the missing line.
for (const [flag, hinted] of [
[undefined, true],
["0", true],
["1", false],
] as const) {
await using proc = Bun.spawn({
cmd: [out],
env: { ...bunEnv, BUN_DEBUG_StandaloneModuleGraph: "1" },
env: {
...bunEnv,
BUN_DEBUG_StandaloneModuleGraph: "1",
BUN_FEATURE_FLAG_DISABLE_STANDALONE_MADVISE: flag,
},
stdout: "pipe",
stderr: "pipe",
});
Expand All @@ -42,7 +56,11 @@ test.skipIf(isWindows || !isDebug)(
expect(stdout).toContain("after-await");
// Scoped loggers write to the debug-writer stream (stdout by default).
// Either the success or failure variant proves the call site is reached.
expect(stdout).toContain("hintSourcePagesDontNeed:");
if (hinted) {
expect(stdout).toContain("hintSourcePagesDontNeed:");
} else {
expect(stdout).not.toContain("hintSourcePagesDontNeed:");
}
expect(stderr).toBe("");
expect(exitCode).toBe(0);
}
Expand Down