Skip to content
Closed
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
1bebf7e
Remove libuv on Windows
dylan-conway Jun 30, 2026
3029673
Fix Windows handle cleanup contracts, worker ref accounting, and lazy…
dylan-conway Jul 1, 2026
8da0320
shell: write regular files synchronously on Windows
dylan-conway Jul 1, 2026
c678475
windows: deliver completions to unref'd handles in idle loop ticks
dylan-conway Jul 1, 2026
bb9fe23
Merge branch 'main' into claude/busy-vaughan-130bdf
dylan-conway Jul 1, 2026
7987ded
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 1, 2026
1d9f7e0
[autofix.ci] apply automated fixes (attempt 2/3)
autofix-ci[bot] Jul 1, 2026
42a9e71
Fix undefined Bun__ensure_winsock on non-Windows builds
dylan-conway Jul 1, 2026
41dd6c9
Fix all clippy errors across the workspace
dylan-conway Jul 1, 2026
e362eeb
Merge remote-tracking branch 'origin/claude/busy-vaughan-130bdf'
dylan-conway Jul 1, 2026
b6bb28c
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 1, 2026
f1fec2c
Fix pread on character devices, ConPTY handle inheritance, and pipe e…
dylan-conway Jul 1, 2026
0685e25
Merge branch 'claude/busy-vaughan-130bdf' of https://github.com/oven-…
dylan-conway Jul 1, 2026
3bd989e
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 1, 2026
7f8ca2f
Fix libuv behavior divergences found by parity audit
dylan-conway Jul 1, 2026
d04833b
Merge branch 'claude/busy-vaughan-130bdf' of https://github.com/oven-…
dylan-conway Jul 1, 2026
eb185ed
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 1, 2026
ea7a056
Fix FileSink data loss, utimes precision, path normalization, and fd …
dylan-conway Jul 1, 2026
8b82489
Merge remote-tracking branch 'origin/main' into claude/busy-vaughan-1…
dylan-conway Jul 1, 2026
a232596
Merge branch 'claude/busy-vaughan-130bdf' of https://github.com/oven-…
dylan-conway Jul 1, 2026
e9ca097
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 1, 2026
0a0de80
Fix two HTTP/3 stalls: starved client Finished and undead conns after…
dylan-conway Jul 1, 2026
e6f15eb
Merge branch 'claude/busy-vaughan-130bdf' of https://github.com/oven-…
dylan-conway Jul 1, 2026
63482d8
Merge remote-tracking branch 'origin/main' into claude/busy-vaughan-1…
dylan-conway Jul 1, 2026
4b47464
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 1, 2026
171ce31
Fix HTTP/3 server stop, listen-failure, and reserved-port handling
dylan-conway Jul 1, 2026
449347e
Merge branch 'claude/busy-vaughan-130bdf' of https://github.com/oven-…
dylan-conway Jul 1, 2026
c35385e
Merge remote-tracking branch 'origin/main' into claude/busy-vaughan-1…
robobun Jul 6, 2026
46c8a52
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 6, 2026
4f64814
win_watcher: use WatchEventKind at the emit call site
robobun Jul 7, 2026
1a4ca2b
Merge remote-tracking branch 'origin/main' into claude/busy-vaughan-1…
robobun Jul 7, 2026
9d7baee
Clean up stale libuv references in doc comments
robobun Jul 7, 2026
235e83a
tty/fsevent: fix window width, malformed-record handling, and stale e…
robobun Jul 7, 2026
1bc44c0
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 7, 2026
8621334
ws2_32: gate every Winsock call at the module boundary
robobun Jul 7, 2026
1a56b1e
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 8, 2026
194d4c0
status.toml + test-harness follow-ups from the tty/signal/fsevent review
robobun Jul 8, 2026
11d868d
Merge remote-tracking branch 'origin/main' into claude/busy-vaughan-1…
robobun Jul 10, 2026
1c39675
Fix silent auto-merge duplications and type changes after merging main
robobun Jul 10, 2026
a19fbf2
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 10, 2026
a4e9ac5
usockets: replace LIBUS_USE_LIBUV guards with LIBUS_USE_BUN_IOCP
robobun Jul 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
59 changes: 36 additions & 23 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 6 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ members = [
"src/collections",
"src/css",
"src/errno",
"src/fdtable",
"src/glob",
"src/http",
"src/install",
"src/install/windows-shim",
"src/parsers",
"src/io",
"src/iocp",
"src/js",
"src/libarchive",
"src/md",
Expand All @@ -28,6 +30,7 @@ members = [
"src/threading",
"src/valkey",
"src/watcher",
"src/winfs",
"src/cares_sys",
"src/clap",
"src/clap_macros",
Expand All @@ -48,7 +51,6 @@ members = [
"src/js_parser_jsc",
"src/js_printer",
"src/libdeflate_sys",
"src/libuv_sys",
"src/ast",
"src/ast_jsc",
"src/picohttp",
Expand Down Expand Up @@ -358,11 +360,13 @@ bun_bundler = { path = "src/bundler" }
bun_collections = { path = "src/collections" }
bun_css = { path = "src/css" }
bun_errno = { path = "src/errno" }
bun_fdtable = { path = "src/fdtable" }
bun_glob = { path = "src/glob" }
bun_http = { path = "src/http" }
bun_install = { path = "src/install" }
bun_parsers = { path = "src/parsers" }
bun_io = { path = "src/io" }
bun_iocp = { path = "src/iocp" }
bun_js = { path = "src/js" }
bun_libarchive = { path = "src/libarchive" }
bun_md = { path = "src/md" }
Expand Down Expand Up @@ -396,7 +400,6 @@ bun_js_parser = { path = "src/js_parser" }
bun_js_parser_jsc = { path = "src/js_parser_jsc" }
bun_js_printer = { path = "src/js_printer" }
bun_libdeflate_sys = { path = "src/libdeflate_sys" }
bun_libuv_sys = { path = "src/libuv_sys" }
bun_ast = { path = "src/ast" }
bun_ast_jsc = { path = "src/ast_jsc" }
bun_picohttp = { path = "src/picohttp" }
Expand All @@ -416,6 +419,7 @@ bun_resolve_builtins = { path = "src/resolve_builtins" }
bun_sourcemap_jsc = { path = "src/sourcemap_jsc" }
bun_url = { path = "src/url" }
bun_windows_sys = { path = "src/windows_sys" }
bun_winfs = { path = "src/winfs" }
bun_jsc = { path = "src/jsc" }
bun_jsc_macros = { path = "src/jsc_macros" }
bun_ptr = { path = "src/ptr" }
Expand Down
7 changes: 7 additions & 0 deletions bench/libuv-removal/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
nullchild.exe
*.pdb
*.ilk
.fixtures/
fixtures/
results/
bench/
92 changes: 92 additions & 0 deletions bench/libuv-removal/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# libuv-removal benchmark program (Windows)

Benchmark harnesses for the libuv-on-Windows removal.
10-agent measurement workflow (8 domain hunters running real measurements on this machine
with bun 1.4.0 release = the "before" build, node 25.8.1 as reference; 2 adversarial
judges), then filtered by the maintainer's rule below.

**Framing rule (maintainer decision): threadpool-shape wins are not removal evidence.**
Anything whose mechanism is "more/better threads" is mitigable by tuning libuv
(`UV_THREADPOOL_SIZE` is honored today) and invites the one-line-env-var rebuttal. The
removal's own evidence is **single-threaded per-op cost** and **IOCP-level async
architecture** (fewer syscalls/round-trips per operation, real overlapped I/O, owned
buffers/semantics). Thread-scheduling findings are kept below as engineering context and
gates, never headlines.

Measurement hygiene (learned the hard way — see script headers): this dev box has
Defender/MsMpEng episodes that inflate absolutes 2-10x for tens of seconds. Every script
uses warmups, interleaved repeats, and medians; **only within-run paired deltas and
within-binary ratios are trustworthy**; before/after comparisons must interleave runs.
Scripts are self-contained, < 30s, most run under both `bun` and `node`.

## Tier 1 — removal-attributable wins (single-threaded / IOCP)

| # | Benchmark | Mechanism deleted | Today → expected | Status |
|---|---|---|---|---|
| 1 | `sync-fs-readsync-positioned.mjs` | libuv emulates POSIX pread as **3 syscalls** (SetFilePointerEx save → ReadFile+OVERLAPPED → restore, fs.c:880-913) + global fd-hash mutex + CRT fd→HANDLE per call; native pread on HANDLE fds is **1 syscall** (already in-tree, unreachable from JS) | positioned read 1.79µs vs sequential 1.16µs — the **+55% ops/s gap closes**; size-independent (pure syscall-pair cost); same dance on the write side and `FileHandle.read` (passes a position by default) | cleanest attribution in the set (same binary, same op); pre-registered prediction |
| 2 | `pipes-stdout-throughput.mjs` (+ `pipes-raw-readfile-control.mjs`) | the zero-read dance: 0-byte ReadFile → IOCP dequeue → second quasi-sync ReadFile (+ fork's PeekNamedPipe gate) per 64KB chunk → native posts **one real overlapped read** into a preallocated buffer, skip-on-success | 2.4-3.0 GB/s today vs **5.1 GB/s same-machine kernel floor** (ffi control); parent CPU 560 → floor 150 ms/GB; publish **after-numbers only** (+30-70% is a projection); lead with CPU-per-GB (less noisy) | hold for after-build; publish full chunk curve incl. flat 256B row (child-bound, annotated) |
| 3 | `pipes-stdin-throughput.mjs` | every chunk pays a **mandatory IOCP completion packet** even on synchronous WriteFile completion (libuv can't skip-on-success on pipes) + single in-flight write serialization | bun 7.4 GB/s vs node 12.4 on the identical kernel path — node proves the headroom | **fix first, publish the recovery after** — today's number is a "bun loses 2x" chart; attribution splits between uv deletion and FileSink rewire (both land in 3.3) |
| 4 | `fswatch-burst.mjs` | libuv hardcodes a **4KB** ReadDirectoryChangesW buffer (fs-event.c:33, kernel-pinned at first call); Bun additionally **silently drops** the overflow signal (win_watcher.rs:237-244). Native: 64KB buffer + overflow→rescan semantics (needs owning the watcher) | blocked-loop burst: **1 of 1000 events delivered today, deterministically** → ~100% + signaled overflow | the reliability anchor (correctness, not µs). Prereq: fix the NULL-drop bug **now** and re-baseline so the before isn't worse than node; buffer size alone is fork-patchable — the rescan semantics are the removal-honest part |
| 5 | `sync-fs-stat-resolver.mjs` | Bun's **own** exists/access uses GetFileAttributesW (~26-30µs under Defender's filter) while the by-name stat class costs ~4µs — this removal consolidates all path metadata onto by-name NT APIs (by-name NT APIs with handle fallback) | existsSync **5-7×** on default-configured Windows (filter-dependent — show Dev Drive run alongside; durable framing is vs-node, which stays on GetFileAttributesW) | honest caveat: this is a syscall-class consolidation the removal *occasions*, not deleted libuv plumbing — consider shipping the `exists_os_path` fix early and re-baselining |
| 6 | `eventloop-setimmediate-chain.mjs` | per-tick self-wake: the uv_async **PQCS packet measured at 200-240ns** (ffi probe) + uv handle-walk/double-update_time bookkeeping (estimated 200-400ns) | 792-941ns/tick loop cost today (microtask control isolates it); target ≤600ns | this removal before/after metric; publish the measured number only; track the microtask-vs-setImmediate gap (machine-speed-immune) |

## Tier 2 — real findings, thread-scheduling shaped (context + gates, not removal headlines)

- `dns-lookup-concurrency.mjs` / `dns-fs-interference.mjs`: libuv's SLOW_IO class caps DNS at
`(4+1)/2 = 2` in-flight per process (threadpool.c:46) — measured 2.5-3.5× hermetically, and
the cap-2 prediction matched measurements exactly. Real, but threadpool-shaped (knob-mitigable
to cap 12) → context. The native DNS migration (this removal) deletes it as a side effect; assert
post-migration that the knob is a no-op. Note: `fetch()` already bypasses this (own pool) —
any claim must say node:dns/net/tls only.
- `asyncfs-bunfile-vs-readfile.mjs`: Bun.file reads = **4+ chained pool hops** (open→fstat→read
loop→close, each a task + wakeup) vs readFile's single task — 1.5-1.9× at concurrency,
knob-proven NOT pool width. The least thread-*count*-shaped of these (it's hop count), and the
after-state is measurable today (readFile parity) — usable as a secondary number framed as
"scheduling round-trips per read: 4→1", or as the "Bun.file reaches parity with its
Linux/macOS architecture" story.
- `asyncfs-workers-global-pool.mjs` / `asyncfs-singlethread-knee.mjs`: the lose-lose
UV_THREADPOOL_SIZE table (wide pool: +60% at 8 workers, **-55..63% at W≤2**) and the 745k
single-thread knee. Gates: post-migration must hold ≥745k at N=32 with no N≤4 regression —
the single most likely place the migration *regresses* (libuv's batched wq drain is genuinely
good at low concurrency).

## Nulls — measured, publish as honesty (what does NOT get faster)

- **Spawn** (`spawn-sync-overhead.mjs`, `spawn-pipe-tax.mjs`, `spawn-async-throughput.mjs`):
Bun is already within 3-5% of raw CreateProcessW in the same binary (ffi control); the
hypothesized stdio-pipe tax measured **0 ± 0.2ms**; fan-out ceiling (~400-520 spawns/s) is
kernel CreateProcessW serialization on both stacks. Post-migration tweet is floor-proximity
("within X µs of CreateProcessW"), never a speedup. Punch-list extracted for follow-up:
lpEnvironment=NULL when env unmodified, cached inheritable NUL handle, skip search_path for
absolute paths. **Regression risk found:** libuv's 64KB pipe buffers are well chosen (a 4KB
control was 60% *slower*) — preserve the sizing.
- **Small-file readFileSync** (`sync-fs-open-close-readfile.mjs`): Defender's filter, not
libuv, owns the cost (5-10% expected) — this is the pre-emptive answer to "I measured
readFileSync and it's the same".
- **Composites** (`composite-startup-resolve.mjs`, `composite-install-extract-link.mjs`,
`composite-test-parallel.mjs`): startup/install/test-orchestration move ~0 — keep as the
per-phase no-regression table ("600-module app start: unchanged through all 4 phases").
- **TCP loopback** (`tcp-loopback-eventing.mjs`): this removal keeps the AFD mechanism; node's
overlapped uv_tcp path is 25-50% faster on loopback today — internal baseline only; records
the business case for a future AFD→overlapped project. Never near marketing.
- **Timers** (`eventloop-timer-precision.mjs` etc.): the 16ms/60fps story requires the
CREATE_WAITABLE_TIMER_HIGH_RESOLUTION decision (plan ADD-02-adjacent) — "unlocked by", never
"delivered by"; node fires *early* (a bug we won't copy) so naive comparisons favor node.
Keep the never-fires-early assertion as the standing gate.

## Side findings (not libuv — spin off separately)

1. `exists_os_path`/access can adopt the by-name stat class **today** (vendored libuv already
proves the 4µs floor in the same binary).
2. `win_watcher.rs:237-244` silently swallows the overflow (NULL-name) event node emits — a
today-fixable bug.
3. **Install hardlink backend is 4-7× slower than the copyfile backend** on the link phase —
dwarfs anything libuv-related for `bun install` on Windows; fix/default-switch before any
Windows install benchmarks ship.
4. `bun -e 1` (45.9-48.1ms) is not faster than `node -e 1` (42.1-47.5ms) on Windows — startup
floor investigation, unrelated to libuv.
5. worker_threads: a worker blocked in top-level await never receives
`parentPort.postMessage` from the parent (node delivers it) — found while building
harnesses.
6. Plan correction (already noted in plan §2.1 errata): SpawnSyncEventLoop is cached in
RareData (`rare_data.rs:741-754`), not created per spawnSync call.
Loading
Loading