Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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

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 — it does not need to be set when running `bun build --compile`. It has no effect on Windows or when running scripts with `bun` directly, since neither releases source pages.

Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
---

## Automatic config loading
Expand Down
4 changes: 4 additions & 0 deletions src/bun_core/env_var.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,10 @@ 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", {});
// Keep the embedded source section of a `bun build --compile` binary
Comment thread
robobun marked this conversation as resolved.
Outdated
// resident: skips the post-entrypoint madvise(MADV_DONTNEED) in
// bun_standalone_graph::Graph::hint_source_pages_dont_need.
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
14 changes: 13 additions & 1 deletion 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 @@ -2229,6 +2230,17 @@ impl StandaloneModuleGraph {
return;
}

if bun_core::env_var::feature_flag::BUN_FEATURE_FLAG_DISABLE_STANDALONE_MADVISE
.get()
.unwrap_or(false)
{
bun_core::scoped_log!(
Comment thread
robobun marked this conversation as resolved.
Outdated
StandaloneModuleGraph,
"hintSourcePagesDontNeed: skipped (BUN_FEATURE_FLAG_DISABLE_STANDALONE_MADVISE)"
);
return;
}

let page: usize = bun_alloc::page_size();
let start = (base as usize) & !(page - 1);
let end_unaligned = base as usize + len;
Expand Down
36 changes: 36 additions & 0 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 @@ -43,6 +45,40 @@ test.skipIf(isWindows || !isDebug)(
// 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:");
expect(stdout).not.toContain("hintSourcePagesDontNeed: skipped");
expect(stderr).toBe("");
expect(exitCode).toBe(0);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
}

// BUN_FEATURE_FLAG_DISABLE_STANDALONE_MADVISE is read by the compiled
// executable at runtime (the binary above was built without it), and a
// falsy value leaves the hint enabled.
for (const [value, skipped] of [
["1", true],
["0", false],
] as const) {
await using proc = Bun.spawn({
cmd: [out],
env: {
...bunEnv,
BUN_DEBUG_StandaloneModuleGraph: "1",
BUN_FEATURE_FLAG_DISABLE_STANDALONE_MADVISE: value,
},
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);

expect(stdout).toContain("before-await");
expect(stdout).toContain("after-await");
if (skipped) {
expect(stdout).toContain("hintSourcePagesDontNeed: skipped (BUN_FEATURE_FLAG_DISABLE_STANDALONE_MADVISE)");
expect(stdout).not.toContain("hintSourcePagesDontNeed: MADV_DONTNEED");
expect(stdout).not.toContain("hintSourcePagesDontNeed: madvise failed");
} else {
expect(stdout).toContain("hintSourcePagesDontNeed:");
expect(stdout).not.toContain("hintSourcePagesDontNeed: skipped");
}
expect(stderr).toBe("");
expect(exitCode).toBe(0);
}
Expand Down