Bun v1.1.10 版本在此!此版本修正了 20 個錯誤。在 Windows 上,未快取的 bun install 速度快了 2 倍。fetch()
使用的記憶體最多減少了 2.8 倍。針對 bun install、原始碼地圖、Windows 可靠性改進和 Node.js 相容性改進進行了多項錯誤修正
我們正在舊金山招聘系統工程師,以打造 JavaScript 的未來!
先前的版本
v1.1.9
修正了 67 個錯誤(解決了 150 個 👍)。修正項目包括:bun install
中的工作區、bun build 中的原始碼地圖、IPv6 和 VPN 連線能力、載入 UNC 路徑、接合點、符號連結以及 Windows 上的 pnpm、fetch()
速度更快、新增dns.prefetch()
API、atob()
速度快了 8 倍、toString('base64url')
速度快了 5 倍、expect().toBeReturned() 匹配器、Node.js 相容性改進、Bun Shell 修正,以及更多錯誤修正。v1.1.8
修正了 54 個錯誤(解決了 184 個 👍)。支援process.on("uncaughtException")
和process.on("unhandledRejection")
、JSON.parse
速度更快、node:zlib
中的 Brotli 支援、Bun API 中的[Symbol.dispose]
、修正了 Windows 上許多當機問題,以及許多其他錯誤修正。v1.1.0
Bundows。Windows 支援登場!
安裝 Bun
curl -fsSL https://bun.dev.org.tw/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
升級 Bun
bun upgrade
fetch
使用更少記憶體
fetch()
在判斷回應本文不再使用時變得更聰明,並更快釋放記憶體。
512 KB 回應(未消耗)
在以下程式碼中,Bun v1.1.10 使用的記憶體比 Bun v1.1.7 少 2.8 倍,比 Node v22 少 3.6 倍。
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",
);
在 macOS arm64 機上執行 20,050 次請求後
執行階段 | 記憶體用量 |
---|---|
Bun v1.1.10 | 166 MB |
Bun v1.1.7 | 467 MB |
Node v22 | 601 MB |
512 KB 回應(消耗 arrayBuffer())
消耗本文也會更快釋放記憶體。在以下程式碼中,Bun v1.1.10 使用的記憶體比 Bun v1.1.7 少 1.6 倍,比 Node v22 少 10 倍。
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",
);
在 macOS arm64 機上執行 20,050 次請求後
執行階段 | 記憶體用量 |
---|---|
Bun v1.1.10 | 285 MB |
Bun v1.1.7 | 469 MB |
Node v22 | 1911 MB |
非常感謝 @cirospaciari 的這項改進!
我們如何讓 fetch 使用更少記憶體
JavaScriptCore 的垃圾收集器讓原生程式碼可以持有 JavaScript 物件的弱參考和強參考。fetch()
特別複雜,因為它牽涉到
Promise
,必須保持存活直到回應完成。Response
,必須至少保持存活直到標頭和狀態碼可用。ReadableStream
,如果讀取,必須在本文下載時保持存活。- 緩衝資料 -
Response
可以將資料緩衝到Uint8Array
、ArrayBuffer
、text
、json
、formData
或blob
中,這些資料必須在本文讀取時保持存活。
首先,這對 Promise
設定了硬性限制 - Promise
物件必須至少保持存活直到我們收到 HTTP 狀態碼和標頭。這表示它是 JSC::Strong
。 最簡單的做法是到此為止,並讓 Response 及其本文保持存活,直到 Response 完成且 HTTP 回應完全讀取。 這是我們在 Bun v1.1.9 和更早版本(此版本之前)中採用的方法。
一旦我們取得標頭和狀態碼,我們就可以釋放 Promise
的強參考。從那時起,我們需要追蹤 Response
物件的生命週期。如果正在讀取回應本文,我們只需要讓 Response
物件在 JavaScript 可觀察到的時間內保持存活。JavaScriptCore 的 JSC::Weak
讓我們可以將終結器附加到 JSC::JSCell
(JavaScript 物件)。此終結器函式會在 JSC::JSCell
從 JavaScript 無法再存取時呼叫,這就是我們如何知道 Response
物件何時不再可供 JavaScript 觀察。
但是,我們需要持有 Response
物件的 JSC::Weak
值,但一旦終結器被呼叫,我們就無法存取 Response
物件本身 - JavaScript 物件在那時已經被釋放。因此,JSC::Strong
和 JSC::Weak
的組合不足以讓我們完全做到這一點。我們需要繼續能夠在 Response
物件不再可供 JavaScript 存取後存取它,以便我們可以處理 ReadableStream
和緩衝資料。
為此,我們採用了常見的手動記憶體管理方法:參考計數。當 fetch
仍有待處理的緩衝或串流資料時,我們會增加 Zig 中 Response
物件的參考計數。當垃圾收集器呼叫 Response
的終結器時,它會減少其參考計數。如果該參考計數為零,則它會釋放 Response 本文和 Response 物件。
當垃圾收集器呼叫 fetch
的終結器,通知它 Response
物件已被收集時,我們現在知道本文是否將不再可存取,並且可以告訴 HTTP 用戶端忽略回應本文。
在 Windows 上,未快取的 bun install
速度快了 2 倍
在 Windows 上,bun install
在解析套件版本時速度提升了 2 倍。
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
我們很快會撰寫一篇部落格文章,詳細說明此改進的技術細節。
已修正:v1.1.9 中 bun install
在 Windows 上掛起的迴歸
您知道 2009 年非阻塞 I/O 是一項新事物嗎?嗯,Windows 上的 bun 使用者體驗了非阻塞 I/O 之前的時代的縮小版。
當系統負載過重或網路不穩定時,bun install
有時會在解析套件版本時無限期掛起。這個問題在 Windows 上早就存在,但在 v1.1.9 中由於實作了 Happy Eyeballs 的子集而加劇。問題在於我們沒有在 Socket 已經連線之後才將 TCP 用戶端連線設為非阻塞。這也會在透過 fetch
、Bun.connect()
、new WebSocket()
連線時造成阻塞 I/O。它不會影響 Bun.serve()
或 Bun.listen()
。
與 v1.1.8(迴歸之前)相比,修正此問題也使 Windows 上 bun install
的效能提升了 20%。
已修正:ENOENT
解析 package.json 錯誤
Bun v1.1.9 中引入的迴歸已修正,在工作區套件內的子資料夾中執行 bun add <package-name>
在某些情況下會失敗,感謝 @dylan-conway。
已修正:使用工作區修正 package-lock.json 遷移
Bun 支援從 package-lock.json
-> bun.lockb
自動遷移,但當該 package-lock.json
包含工作區套件時,可能會發生錯誤。這已修正,感謝 @dylan-conway。
已修正:修改「overrides」或「resolutions」後當機
在修改 package.json
中的 overrides
或 resolutions
欄位後,下一次 bun install
可能會發生的當機問題已修正,感謝 @gvilums。
新增:bun:test 中的 setDefaultTimeout
bun:test
現在支援 setDefaultTimeout
函式,以修改目前範圍或模組中測試的預設逾時。
import { test, setDefaultTimeout } from "bun:test";
// Timeout after 10 milliseconds:
setDefaultTimeout(10);
test("timeout", async () => {
await Bun.sleep(9999999);
});
先前,您必須個別設定每個測試的逾時
import { test } from "bun:test";
test("timeout", async () => {
await Bun.sleep(9999999);
}, 10);
您仍然可以個別設定每個測試的逾時。
為了與 Jest 相容,我們也在 bun:test
中實作了 jest.setTimeout
。
import { test, jest } from "bun:test";
jest.setTimeout(10);
test("timeout", async () => {
await Bun.sleep(9999999);
});
我們選擇將 bun:test
的版本命名為 "setDefaultTimeout"
而不是 "setTimeout"
,以避免與全域 setTimeout
計時器函式混淆。
感謝 @dylan-conway 的這項功能!
已修正:macOS 上衍生程序中可能發生的掛起
已修正衍生程序在從 stdout、stderr 或 stdin 讀取時可能無限期掛起的錯誤。
為什麼會發生這種情況?
在 macOS 上,使用 MSG_NOWAIT
旗標和阻塞 Socket 呼叫 send(2)
仍然會阻塞。在 Linux 上,MSG_NOWAIT
會使其不阻塞。因此,如果您設法將大量資料傳送到 Socket,則父程序可能會阻塞。
與管道不同,將 Socket 的一端標記為非阻塞對另一端是不可觀察的。
為了驗證這一點,我們可以編譯以下 C 程式
#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;
}
然後在 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());
輸出將為
stdout is blocking
stderr is blocking
stdin is blocking
此輸出是正確的,因為阻塞 stdout、stderr 和 stdin 對於許多 UNIX 程式正常運作是必要的。例如,如果 stdout 是非阻塞並傳回 EAGAIN
,則 cat
將無法正常運作。我們希望 Bun 沒有阻塞 stdout、stderr 和 stdin,但依賴阻塞 stdout、stderr 和 stdin 的程式繼續正常運作非常重要。
因此,我們可以安全地將 Socket 標記為非阻塞,而不會影響另一端。
為了防止未來發生迴歸,我們新增了一個迴歸測試,在正確的條件下將大量資料寫入和讀取到另一個程序,以導致此錯誤,並驗證此測試先前在 macOS 上失敗。
已修正:bun build
中使用 --splitting
時,原始碼地圖 JSON 無效
已修正一個錯誤,在某些情況下,當在 bun build
中使用 --splitting
時,原始碼地圖不會產生正確的 JSON 物件,感謝 @paperclover。此錯誤是由於錯誤地聯接來自不同檔案的原始碼地圖字串所致。為了防止未來發生迴歸,我們改進了測試覆蓋率以驗證原始碼地圖是否有效。
Bun 中原始碼地圖的更多改進即將推出。
Windows 修正
已修正:Worker 結束時延遲 1 秒
一個事件迴圈錯誤導致 Worker 在 Windows 上結束時延遲 1 秒。這已修正,感謝 @gvilums。
bun build
中匯入路徑中的反斜線
已修正一個錯誤,其中未逸出的反斜線可能會插入到 bun build
的匯入規範中,感謝 @paperclover。
已修正:bun --watch
未終止 Bun 執行個體
先前,當使用 bun --watch
時,Bun 的執行個體可能會在退出後繼續執行。 此問題已修正,感謝 @paperclover。
Node.js 相容性改進
已修正:串流錯誤中的 EventEmitter
已修正一個錯誤,其中在 emit
呼叫中移除的事件接聽器會被略過。此錯誤影響了 node:stream
,但不影響 node:events
。感謝 @gvilums 修正此問題。
此錯誤影響了關閉連線時的 Postgres.js。
當機報告上傳
在 macOS 和 Linux 上,當發生當機時,Bun 會嘗試自動將 bun.report 連結上傳到我們的當機報告服務。這將有助於我們更快地修正錯誤。您可以透過設定 BUN_CRASH_REPORTER_URL=""
來停用此功能。請繼續在 GitHub 上向我們報告當機問題,因為這確實有助於我們修正錯誤。深入瞭解 bun.report。
在 Linux 上,這在 Bun 的 Canary 組建中已啟用,但在發行組建中未啟用。