Bun v1.1.10 is here! This release fixes 20 bugs. 2x faster uncached bun install on Windows. fetch()
uses up to 2.8x less memory. Several bugfixes to bun install, sourcemaps, Windows reliability improvements and Node.js compatibility improvements
We're hiring systems engineers in San Francisco to build the future of JavaScript!
Previous releases
v1.1.9
fixes 67 bugs (addressing 150 👍). Fixes to: workspaces inbun install
, sourcemaps in bun build, IPv6 & VPN connectivity, Loading UNC paths, junctions, symlinks, and pnpm on Windows,fetch()
gets faster, addeddns.prefetch()
API,atob()
gets 8x faster,toString('base64url')
gets 5x faster, expect().toBeReturned() matcher, Node.js compatibility improvements, Bun Shell fixes, and lots more bugfixes.v1.1.8
fixes 54 bugs (addressing 184 👍). Support forprocess.on("uncaughtException")
andprocess.on("unhandledRejection")
,JSON.parse
gets faster, Brotli support innode:zlib
,[Symbol.dispose]
in Bun APIs, fixes lots of crashes on Windows, and many other bugfixes.v1.1.0
Bundows. Windows support is here!
To install Bun
curl -fsSL https://bun.sh/install | bash
npm install -g bun
powershell -c "irm bun.sh/install.ps1|iex"
scoop install bun
brew tap oven-sh/bun
brew install bun
docker pull oven/bun
docker run --rm --init --ulimit memlock=-1:-1 oven/bun
To upgrade Bun
bun upgrade
fetch
uses less memory
fetch()
gets smarter about when the response body is no longer in use, and releases the memory sooner.
512 KB responses (unconsumed)
In the following code, Bun v1.1.10 uses 2.8x less memory than Bun v1.1.7, and 3.6x less memory than Node v22.
const server = process.argv.at(-1);
const fmt = new Intl.NumberFormat();
let total = 0;
const batch = 50;
const delay = 32;
while (total < 20_000) {
for (let i = 0; i < batch; i++) {
fetch(server);
}
await new Promise((r) => setTimeout(r, delay));
total += batch;
}
console.log(
"RSS",
(process.memoryUsage.rss() / 1024 / 1024) | 0,
"MB after",
fmt.format((total += batch)) + " fetch() requests",
);
After 20,050 requests on a macOS arm64 machine:
Runtime | Memory usage |
---|---|
Bun v1.1.10 | 166 MB |
Bun v1.1.7 | 467 MB |
Node v22 | 601 MB |
512 KB responses (consuming arrayBuffer())
Consuming the body also releases memory sooner. In the following code, Bun v1.1.10 uses 1.6x less memory than Bun v1.1.7, and 10x less memory than Node v22.
const server = process.argv.at(-1);
const fmt = new Intl.NumberFormat();
let total = 0;
const batch = 50;
const delay = 32;
while (total < 20_000) {
for (let i = 0; i < batch; i++) {
fetch(server).then((r) => r.arrayBuffer());
}
await new Promise((r) => setTimeout(r, delay));
total += batch;
}
console.log(
"RSS",
(process.memoryUsage.rss() / 1024 / 1024) | 0,
"MB after",
fmt.format((total += batch)) + " fetch() requests",
);
After 20,050 requests on a macOS arm64 machine:
Runtime | Memory usage |
---|---|
Bun v1.1.10 | 285 MB |
Bun v1.1.7 | 469 MB |
Node v22 | 1911 MB |
Huge thanks to @cirospaciari for this improvement!
How we made fetch use less memory
JavaScriptCore's garbage collector lets native code hold weak and strong references to JavaScript objects. fetch()
is especially complicated because it involves:
Promise
, which must be kept alive until the Response is fulfilled.Response
, which must be kept alive at least until the headers and status code become available.ReadableStream
, which, if read, must be kept alive while the body is being downloaded.- Buffered data -
Response
can buffer data into aUint8Array
,ArrayBuffer
,text
,json
,formData
, orblob
, which must be kept alive while the body is being read.
First, this sets a hard constraint on Promise
- the Promise
object must be kept alive at least until we receive the HTTP status code and headers. That means it's a JSC::Strong
. The easy thing to do would be to stop here and just keep the Response and it's body alive until the Response is fulfilled and the HTTP response is completely read. This is the approach we took in Bun v1.1.9 and earlier (before this release).
Once we have the headers and status code, we can release the Promise
's strong reference. From there, we need to track the lifetime of the Response
object. We only need to keep the Response
object alive as long as it is observable to JavaScript, if the response body is being read. JavaScriptCore's JSC::Weak
let's us attach a finalizer to a JSC::JSCell
(a JavaScript object). This finalizer function is called after the JSC::JSCell
is no longer reachable from JavaScript, which is how we know when the Response
object is no longer observable to JavaScript.
However, we need to hold a JSC::Weak
value to the Response
object, but we cannot access the Response
object itself once the finalizer is called - the JavaScript object is already freed by then. So the combination of JSC::Strong
and JSC::Weak
is not enough to get us all the way here. We need to continue to be able to access the Response
object after it's no longer accessible to JavaScript, so that we can handle the ReadableStream
and the buffered data.
For this, we turned to a common manual memory management approach: reference counting. When there's still pending buffered or streaming data from fetch
, we increment the reference count on the Response
object in Zig. When the garbage collector calls Response
's finalizer, it decrements its reference count. If that reference count is zero, then it frees the Response body and the Response object.
When the garbage collector calls fetch
's finalizer notifying it that the Response
object has been collected, we now know if the body will no longer be accessible and can tell the HTTP client to ignore the response body.
2x faster uncached bun install
on Windows
On Windows, bun install
gets a 2x speedup when resolving package versions.
PS C:\bun> hyperfine "bun install --ignore-scripts" "bun-1.1.8 install --ignore-scripts" --prepare="del /s /q bun.lockb && del /s /q C:\Users\window\.bun\install\cache" --warmup=1
Benchmark 1: bun install --ignore-scripts
Time (mean ± σ): 1.343 s ± 0.398 s [User: 0.321 s, System: 0.178 s]
Range (min … max): 0.830 s … 1.861 s 10 runs
Benchmark 2: bun-1.1.8 install --ignore-scripts
Time (mean ± σ): 3.997 s ± 0.204 s [User: 0.264 s, System: 0.192 s]
Range (min … max): 3.753 s … 4.409 s 10 runs
Summary
bun install --ignore-scripts ran
2.98 ± 0.89 times faster than bun-1.1.8 install --ignore-scripts
We will write a blog post on the technical details of this improvement soon.
Fixed: Regression in v1.1.9 with hanging bun install
on Windows
You know how back in the year 2009, non-blocking I/O was a new thing? Well, bun users on Windows experienced a small version of what the time before non-blocking I/O was like.
bun install
would sometimes hang indefinitely when resolving package versions while the system was under load or the network was flaky. This problem existed earlier on Windows, but was exacerbated in v1.1.9 due to implementing a subset of Happy Eyeballs. The issue was that we were not making the TCP client connection non-blocking until after the socket was already connected. This also caused blocking I/O when connecting via fetch
, Bun.connect()
, new WebSocket()
. It did not impact Bun.serve()
or Bun.listen()
.
Fixing this also led to a 20% performance improvement to bun install
on Windows, compared to v1.1.8 (before the regression).
Fixed: ENOENT
parsing package.json error
A regression introduced in Bun v1.1.9 where bun add <package-name>
inside a subfolder within a workspace package would fail in certain cases has been fixed, thanks to @dylan-conway.
Fixed: package-lock.json migration with workspaces fix
Bun supports automatically migrating from package-lock.json
-> bun.lockb
, but an error could occur when that package-lock.json
contained workspace packages. This has been fixed, thanks to @dylan-conway.
Fixed: Crash after modifying "overrides" or "resolutions"
A crash that could occur on the next bun install
after modifying the overrides
or resolutions
fields in package.json
has been fixed, thanks to @gvilums.
New: setDefaultTimeout
in bun:test
bun:test
now supports the setDefaultTimeout
function to modify the default timeout for tests in the current scope or module.
import { test, setDefaultTimeout } from "bun:test";
// Timeout after 10 milliseconds:
setDefaultTimeout(10);
test("timeout", async () => {
await Bun.sleep(9999999);
});
Previously, you'd have to set the timeout for each test individually:
import { test } from "bun:test";
test("timeout", async () => {
await Bun.sleep(9999999);
}, 10);
You can still set the timeout for each test individually.
For Jest compatibility, we've also implemented jest.setTimeout
in bun:test
.
import { test, jest } from "bun:test";
jest.setTimeout(10);
test("timeout", async () => {
await Bun.sleep(9999999);
});
We chose to name bun:test
's version "setDefaultTimeout"
instead of "setTimeout"
to avoid confusion with the global setTimeout
timer function.
Thanks to @dylan-conway for this feature!
Fixed: Potential hang in spawned processes on macOS
A bug where spawned processes could hang indefinitely while reading from stdout, stderr, or stdin has been fixed.
Why did this happen?
On macOS, calling send(2)
with the MSG_NOWAIT
flag with a blocking socket still blocks. On Linux, MSG_NOWAIT
causes it to not block. So if you managed to send a very large amount of data to a socket, the parent process would potentially block.
Unlike pipes, marking one end of a socket as non-blocking is not observable to the other end.
To verify this, we can compile the following C program:
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
int main() {
// Sleep for 10 seconds so that the other process has time to mark stdout as
// non-blocking
sleep(10);
// Check if stdout is blocking or non-blocking
int stdout_flags = fcntl(fileno(stdout), F_GETFL);
if (stdout_flags & O_NONBLOCK) {
printf("stdout is non-blocking\n");
} else {
printf("stdout is blocking\n");
}
// Check if stderr is blocking or non-blocking
int stderr_flags = fcntl(fileno(stderr), F_GETFL);
if (stderr_flags & O_NONBLOCK) {
printf("stderr is non-blocking\n");
} else {
printf("stderr is blocking\n");
}
// Check if stdin is blocking or non-blocking
int stdin_flags = fcntl(fileno(stdin), F_GETFL);
if (stdin_flags & O_NONBLOCK) {
printf("stdin is non-blocking\n");
} else {
printf("stdin is blocking\n");
}
return 0;
}
And then spawn it in Bun:
import { spawn, $ } from "bun";
await $`cc -o a.out a.c`;
const { stdout } = spawn({
cmd: ["a.out"],
stdout: "pipe",
stderr: "pipe",
stdin: "pipe",
});
console.log(await new Response(stdout).text());
The output will be:
stdout is blocking
stderr is blocking
stdin is blocking
This output is correct because blocking stdout, stderr, and stdin are necessary for many UNIX programs to work correctly. For example, cat
will not work correctly if stdout is non-blocking and returns EAGAIN
. We want Bun to not have blocking stdout, stderr, and stdin, but it's very important that programs relying on blocking stdout, stderr, and stdin continue to work correctly.
So we can safely mark the socket as non-blocking without affecting the other end.
To prevent future regressions, we've added a regression test that writes & reads large amounts of data to another process in the right conditions to cause this error, and verified that this test previously failed on macOS.
Fixed: Sourcemaps invalid JSON with --splitting
in bun build
A bug where sourcemaps in certain cases were not generating a correct JSON object when using --splitting
in bun build
has been fixed, thanks to @paperclover. This bug was caused by incorrectly joining sourcemap strings from separate files. To prevent future regressions, we've improved our test coverage to verify sourcemaps are valid.
More improvements to sourcemaps in Bun are coming soon.
Windows fixes
Fixed: 1s delay to Worker exit
An event loop bug caused a 1s delay to Worker exit on Windows. This has been fixed, thanks to @gvilums.
Backslashes in import paths in bun build
A bug where an unescaped backslash could be inserted in bun build
's import specifiers has been fixed, thanks to @paperclover.
Fixed: bun --watch
not killing instances of Bun
Previously, when using bun --watch
, instances of Bun could be left running after exiting. This issue has been fixed, thanks to @paperclover.
Node.js compatibility improvements
Fixed: EventEmitter in streams bug
A bug where event listeners removed within an emit
call would be skipped has been fixed. This bug impacted node:stream
, but did not impact node:events
. Thanks to @gvilums for fixing this.
This bug impacted Postgres.js when closing connections.
Crash report uploading
On macOS & Linux, Bun will attempt to automatically upload bun.report links to our crash reporting service when a crash occurs. This will help us fix bugs faster. You can disable this by setting BUN_CRASH_REPORTER_URL=""
. Please continue to report crashes to us on GitHub, as it really helps us fix bugs. Learn more bun.report.
On Linux, this is enabled in the canary build of Bun but not on the release build.