Skip to content

Navigation Menu

Sign in
Sign up

deps: update libarchive to v3.7.8 - #13

Open
github-actions[bot] wants to merge 1 commit into
main from
deps/update-libarchive-1
Open

deps: update libarchive to v3.7.8 #13
github-actions[bot] wants to merge 1 commit into
main from
deps/update-libarchive-1

Conversation

@github-actions

@github-actions github-actions Bot commented Mar 30, 2025

Copy link
Copy Markdown

What does this PR do?

Updates libarchive to version v3.7.8

Compare: libarchive/libarchive@898dc83...e317477

Auto-updated by this workflow

pull Bot pushed a commit that referenced this pull request Jul 25, 2025
...ck traces upon crash in CI (oven-sh#21143)
### What does this PR do?
Closes oven-sh#13012
On Linux, when any Bun process spawned by `runner.node.mjs` crashes, we
run GDB in batch mode to print a backtrace from the core file.
And on all platforms, we run a mini `bun.report` server which collects
crashes reported by any Bun process executed during the tests, and after
each test `runner.node.mjs` fetches and prints any new crashes from the
server.
<details>
<summary>example 1</summary>
```
#0 crash_handler.crash () at crash_handler.zig:1513
#1 0x0000000002cf4020 in crash_handler.crashHandler (reason=..., error_return_trace=0x0, begin_addr=...) at crash_handler.zig:479
#2 0x0000000002cefe25 in crash_handler.handleSegfaultPosix (sig=<optimized out>, info=<optimized out>) at crash_handler.zig:800
#3 0x00000000045a1124 in WTF::jscSignalHandler (sig=11, info=0x7ffe044e30b0, ucontext=0x0) at vendor/WebKit/Source/WTF/wtf/threads/Signals.cpp:548
#4 <signal handler called>
#5 JSC::JSCell::type (this=0x0) at vendor/WebKit/Source/JavaScriptCore/runtime/JSCellInlines.h:137
#6 JSC::JSObject::getOwnNonIndexPropertySlot (this=0x150bc914fe18, vm=..., structure=0x150a0102de50, propertyName=..., slot=...) at vendor/WebKit/Source/JavaScriptCore/runtime/JSObject.h:1348
#7 JSC::JSObject::getPropertySlot<false> (this=0x150bc914fe18, globalObject=0x150b864e0088, propertyName=..., slot=...) at vendor/WebKit/Source/JavaScriptCore/runtime/JSObject.h:1433
#8 JSC::JSValue::getPropertySlot (this=0x7ffe044e4880, globalObject=0x150b864e0088, propertyName=..., slot=...) at vendor/WebKit/Source/JavaScriptCore/runtime/JSCJSValueInlines.h:1108
#9 JSC::JSValue::get (this=0x7ffe044e4880, globalObject=0x150b864e0088, propertyName=..., slot=...) at vendor/WebKit/Source/JavaScriptCore/runtime/JSCJSValueInlines.h:1065
#10 JSC::LLInt::performLLIntGetByID (bytecodeIndex=..., codeBlock=0x150b861e7740, globalObject=0x150b864e0088, baseValue=..., ident=..., metadata=...) at vendor/WebKit/Source/JavaScriptCore/llint/LLIntSlowPaths.cpp:878
#11 0x0000000004d7b055 in llint_slow_path_get_by_id (callFrame=0x7ffe044e4ab0, pc=0x150bc92ea0e7) at vendor/WebKit/Source/JavaScriptCore/llint/LLIntSlowPaths.cpp:946
#12 0x0000000003dd6042 in llint_op_get_by_id ()
#13 0x0000000000000000 in ?? ()
```
</details>
<details>
<summary>example 2</summary>
```
 #0 crash_handler.crash () at crash_handler.zig:1513
 #1 0x0000000002c5db80 in crash_handler.crashHandler (reason=..., error_return_trace=0x0, begin_addr=...) at crash_handler.zig:479
 #2 0x0000000002c59f60 in crash_handler.handleSegfaultPosix (sig=<optimized out>, info=<optimized out>) at crash_handler.zig:800
 #3 0x00000000042ecc88 in WTF::jscSignalHandler (sig=11, info=0xfffff60141b0, ucontext=0xfffff6014230) at vendor/WebKit/Source/WTF/wtf/threads/Signals.cpp:548
 #4 <signal handler called>
 #5 bun.js.api.FFIObject.Reader.u8 (globalObject=0x4000554e0088) at /var/lib/buildkite-agent/builds/ip-172-31-75-92/bun/bun/src/bun.js/api/FFIObject.zig:65
 #6 bun.js.jsc.host_fn.toJSHostCall__anon_1711576 (globalThis=0x4000554e0088, args=...) at /var/lib/buildkite-agent/builds/ip-172-31-75-92/bun/bun/src/bun.js/jsc/host_fn.zig:97
 #7 bun.js.jsc.host_fn.DOMCall("Reader"[0..6],bun.js.api.FFIObject.Reader,"u8"[0..2],.{ .reads = .{ ... }, .writes = .{ ... } }).slowpath (globalObject=0x4000554e0088, thisValue=70370172175040, arguments_ptr=0xfffff6015460, arguments_len=1) at /var/lib/buildkite-agent/builds/ip-172-31-75-92/bun/bun/src/bun.js/jsc/host_fn.zig:490
 #8 0x000040003419003c in ?? ()
 #9 0x0000400055173440 in ?? ()
```
</details>
I used GDB instead of LLDB (as the branch name suggests) because it
seems to produce more useful stack traces with musl libc.
- [x] on linux, use gdb to print from core dump of main bun process
crashed
- [x] on linux, use gdb to print from all new core dumps (so including
bun subprocesses spawned by the test that crashed)
- [x] on all platforms, use a mini bun.report server to print a
self-reported trace (depends on oven-sh/bun.report#15; for now our
package.json points to a commit on the branch of that repo)
- [x] fix trying to fetch stack traces too early on windows
- [x] use output groups so the traces show up alongside the log for the
specific test instead of having to find it in the logs from the entire
run
- [x] get oven-sh/bun.report#15 merged, and point to a bun.report commit
on the main branch instead of the PR branch in package.json
### How did you verify your code works?
Manually, and in CI with a crashing test.
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
pull Bot pushed a commit that referenced this pull request Aug 8, 2025
<details>
<summary> observed in
https://buildkite.com/bun/bun/builds/22442#annotation-test/js/node/zlib/leak.test.ts
</summary>
```
==5045==ERROR: AddressSanitizer: heap-use-after-free on address 0x5220000243c0 at pc 0x00000dad671b bp 0x14f22d4a4990 sp 0x14f22d4a4988
READ of size 8 at 0x5220000243c0 thread T5 (HeapHelper)
======== Stack trace from GDB for HeapHelper-5045.core: ========
Program terminated with signal SIGABRT, Aborted.
#0 0x000014f2c3672eec in ?? () from /lib/x86_64-linux-gnu/libc.so.6
[Current thread is 1 (Thread 0x14f22d4f46c0 (LWP 5050))]
#0 0x000014f2c3672eec in ?? () from /lib/x86_64-linux-gnu/libc.so.6
#1 0x000014f2c3623fb2 in raise () from /lib/x86_64-linux-gnu/libc.so.6
#2 0x000014f2c360e472 in abort () from /lib/x86_64-linux-gnu/libc.so.6
#3 0x000000000e3b2ae2 in uw_init_context_1[cold] ()
#4 0x000000000e3b29fc in _Unwind_Backtrace ()
#5 0x00000000046a6bab in __sanitizer::BufferedStackTrace::UnwindSlow(unsigned long, unsigned int) ()
#6 0x00000000046a181d in __sanitizer::BufferedStackTrace::Unwind(unsigned int, unsigned long, unsigned long, void*, unsigned long, unsigned long, bool) ()
#7 0x00000000046885bd in __sanitizer::BufferedStackTrace::UnwindImpl(unsigned long, unsigned long, void*, bool, unsigned int) ()
#8 0x0000000004601127 in __asan::ErrorGeneric::Print() ()
#9 0x0000000004683180 in __asan::ScopedInErrorReport::~ScopedInErrorReport() ()
#10 0x0000000004686567 in __asan::ReportGenericError(unsigned long, unsigned long, unsigned long, unsigned long, bool, unsigned long, unsigned int, bool) ()
#11 0x0000000004686d46 in __asan_report_load8 ()
#12 0x000000000dad671b in ZSTD_sizeof_CCtx (cctx=<optimized out>) at ./build/release-asan/zstd/vendor/zstd/lib/compress/zstd_compress.c:210
#13 0x0000000006d2284d in bun.js.node.zlib.NativeZstd.estimatedSize () at /var/lib/buildkite-agent/builds/ip-172-31-72-121/bun/bun/src/bun.js/node/zlib/NativeZstd.zig:57
#14 ZigGeneratedClasses.JSNativeZstd.JavaScriptCoreBindings.NativeZstd__estimatedSize (thisValue=<optimized out>) at /var/lib/buildkite-agent/builds/ip-172-31-72-121/bun/bun/build/release-asan/codegen/ZigGeneratedClasses.zig:11122
#15 0x000000000852803b in WebCore::JSNativeZstd::visitChildrenImpl<JSC::SlotVisitor> (cell=0x14f22e190840, visitor=...) at ./build/release-asan/./build/release-asan/codegen/ZigGeneratedClasses.cpp:30728
#16 WebCore::JSNativeZstd::visitChildren (cell=0x14f22e190840, visitor=...) at ./build/release-asan/./build/release-asan/codegen/ZigGeneratedClasses.cpp:30734
#17 0x000000000aa99d6c in JSC::MethodTable::visitChildren (this=<optimized out>, cell=<optimized out>, visitor=...) at vendor/WebKit/Source/JavaScriptCore/runtime/ClassInfo.h:115
#18 0x000000000aa99d6c in JSC::SlotVisitor::visitChildren (this=0x14f277028300, cell=0x14f22e190840)
#19 JSC::SlotVisitor::drain(WTF::MonotonicTime)::$_0::operator()(JSC::MarkStackArray&) const (this=<optimized out>, stack=...) at vendor/WebKit/Source/JavaScriptCore/heap/SlotVisitor.cpp:509
#20 0x000000000aa8f130 in JSC::SlotVisitor::forEachMarkStack<JSC::SlotVisitor::drain(WTF::MonotonicTime)::$_0>(JSC::SlotVisitor::drain(WTF::MonotonicTime)::$_0 const&) (this=0x14f277028300, func=...) at vendor/WebKit/Source/JavaScriptCore/heap/SlotVisitorInlines.h:193
#21 JSC::SlotVisitor::drain (this=this@entry=0x14f277028300, timeout=<error reading variable: That operation is not available on integers of more than 8 bytes.>, timeout@entry=...) at vendor/WebKit/Source/JavaScriptCore/heap/SlotVisitor.cpp:499
#22 0x000000000aa90590 in JSC::SlotVisitor::drainFromShared (this=0x14f277028300, sharedDrainMode=JSC::SlotVisitor::HelperDrain, timeout=<error reading variable: That operation is not available on integers of more than 8 bytes.>) at vendor/WebKit/Source/JavaScriptCore/heap/SlotVisitor.cpp:699
#23 0x000000000aa08726 in JSC::Heap::runBeginPhase(JSC::GCConductor)::$_1::operator()() const (this=<optimized out>) at vendor/WebKit/Source/JavaScriptCore/heap/Heap.cpp:1508
#24 WTF::SharedTaskFunctor<void (), JSC::Heap::runBeginPhase(JSC::GCConductor)::$_1>::run() (this=<optimized out>) at .WTF/Headers/wtf/SharedTask.h:91
#25 0x000000000aa3b596 in WTF::ParallelHelperClient::runTask(WTF::RefPtr<WTF::SharedTask<void ()>, WTF::RawPtrTraits<WTF::SharedTask<void ()> >, WTF::DefaultRefDerefTraits<WTF::SharedTask<void ()> > > const&) (this=0x14f22e000428, task=...) at vendor/WebKit/Source/WTF/wtf/ParallelHelperPool.cpp:110
#26 0x000000000aa3d976 in WTF::ParallelHelperPool::Thread::work (this=<optimized out>) at vendor/WebKit/Source/WTF/wtf/ParallelHelperPool.cpp:201
#27 0x000000000aa4210d in WTF::AutomaticThread::start(WTF::AbstractLocker const&)::$_0::operator()() const (this=<optimized out>) at vendor/WebKit/Source/WTF/wtf/AutomaticThread.cpp:225
#28 WTF::Detail::CallableWrapper<WTF::AutomaticThread::start(WTF::AbstractLocker const&)::$_0, void>::call() (this=<optimized out>) at vendor/WebKit/Source/WTF/wtf/Function.h:53
#29 0x0000000008958ada in WTF::Function<void ()>::operator()() const (this=<optimized out>) at vendor/WebKit/Source/WTF/wtf/Function.h:82
#30 WTF::Thread::entryPoint (newThreadContext=<optimized out>) at vendor/WebKit/Source/WTF/wtf/Threading.cpp:272
#31 0x0000000008a65689 in WTF::wtfThreadEntryPoint (context=0x13b5) at vendor/WebKit/Source/WTF/wtf/posix/ThreadingPOSIX.cpp:255
#32 0x000000000467d347 in asan_thread_start(void*) ()
#33 0x000014f2c36711f5 in ?? () from /lib/x86_64-linux-gnu/libc.so.6
#34 0x000014f2c36f189c in ?? () from /lib/x86_64-linux-gnu/libc.so.6
```
</details>
`ZSTD_sizeof_CCtx` and `ZSTD_sizeof_DCtx` can not be relied upon to be
thread-safe and estimatedSize may be called from any thread
pull Bot pushed a commit that referenced this pull request Aug 20, 2025
...Worker" (oven-sh#21994)
Reverts oven-sh#21962
`vm.ensureTerminationException` allocates a JSString, which is not safe
to do from a thread that doesn't own the API lock.
```ts
Bun Canary v1.2.21-canary.1 (f706382a) Linux x64 (baseline)
Linux Kernel v6.12.38 | musl
CPU: sse42 popcnt avx avx2 avx512
Args: "/var/lib/buildkite-agent/builds/ip-172-31-38-185/bun/bun/release/bun-linux-x64-musl-baseline-profile/bun-profile" "/var/lib/buildkite-agent/builds/ip-172-31-38-185/bun/bun/test/js/node/worker_threads"...
Features: bunfig http_server jsc tsconfig(3) tsconfig_paths workers_spawned(40) workers_terminated(34)
Builtins: "bun:main" "node:worker_threads"
Elapsed: 362ms | User: 518ms | Sys: 63ms
RSS: 0.34GB | Peak: 100.36MB | Commit: 0.34GB | Faults: 0 | Machine: 8.17GB
 
panic(main thread): Segmentation fault at address 0x0
oh no: Bun has crashed. This indicates a bug in Bun, not your code.
 
To send a redacted crash report to Bun's team,
please file a GitHub issue using the link below:
 
 http://localhost:38809/1.2.21/Ba2f706382wNgkgUu11luEm6yX+lwy+Dgtt+oEurthoD8214mE___07+09DA2AA
 
 
 6 | describe("Worker destruction", () => {
 7 | const method = ["Bun.connect", "Bun.listen", "fetch"];
 8 | describe.each(method)("bun when %s is used in a Worker that is terminating", method => {
 9 | // fetch: ASAN failure
10 | test.skipIf(isBroken && method == "fetch")("exits cleanly", () => {
11 | expect([join(import.meta.dir, "worker_thread_check.ts"), method]).toRun();
 ^
error:
 
Command /var/lib/buildkite-agent/builds/ip-172-31-38-185/bun/bun/test/js/node/worker_threads/worker_thread_check.ts Bun.connect failed:
Spawned 10 workers RSS 79 MB
Spawned 10 workers RSS 87 MB
Spawned 10 workers RSS 90 MB
 
 at <anonymous> (/var/lib/buildkite-agent/builds/ip-172-31-38-185/bun/bun/test/js/node/worker_threads/worker_destruction.test.ts:11:73)
✗ Worker destruction > bun when Bun.connect is used in a Worker that is terminating > exits cleanly [597.56ms]
✓ Worker destruction > bun when Bun.listen is used in a Worker that is terminating > exits cleanly [503.47ms]
» Worker destruction > bun when fetch is used in a Worker that is terminating > exits cleanly
 
 
 1 pass
 1 skip
 1 fail
 2 expect() calls
Ran 3 tests across 1 file. [1125.00ms]
======== Stack trace from GDB for bun-profile-28234.core: ========
Program terminated with signal SIGILL, Illegal instruction.
#0 crash_handler.crash () at crash_handler.zig:1523
[Current thread is 1 (LWP 28234)]
#0 crash_handler.crash () at crash_handler.zig:1523
#1 0x0000000002db77aa in crash_handler.crashHandler (reason=..., error_return_trace=0x0, begin_addr=...) at crash_handler.zig:471
#2 0x0000000002db2b55 in crash_handler.handleSegfaultPosix (sig=<optimized out>, info=<optimized out>) at crash_handler.zig:792
#3 0x0000000004716b58 in WTF::jscSignalHandler (sig=11, info=0x7ffe54051e90, ucontext=0x0) at vendor/WebKit/Source/WTF/wtf/threads/Signals.cpp:548
#4 <signal handler called>
#5 JSC::VM::currentThreadIsHoldingAPILock (this=0x148296c30000) at vendor/WebKit/Source/JavaScriptCore/runtime/VM.h:840
#6 JSC::sanitizeStackForVM (vm=...) at vendor/WebKit/Source/JavaScriptCore/runtime/VM.cpp:1369
#7 0x0000000003f4a060 in JSC::LocalAllocator::allocate(JSC::Heap&, unsigned long, JSC::GCDeferralContext*, JSC::AllocationFailureMode)::{lambda()#1}::operator()() const (this=<optimized out>) at cache/webkit-a73e665a39b281c5/include/JavaScriptCore/LocalAllocatorInlines.h:46
#8 JSC::FreeList::allocateWithCellSize<JSC::LocalAllocator::allocate(JSC::Heap&, unsigned long, JSC::GCDeferralContext*, JSC::AllocationFailureMode)::{lambda()#1}>(JSC::LocalAllocator::allocate(JSC::Heap&, unsigned long, JSC::GCDeferralContext*, JSC::AllocationFailureMode)::{lambda()#1} const&, unsigned long) (this=0x148296c38e48, cellSize=16, slowPath=...) at cache/webkit-a73e665a39b281c5/include/JavaScriptCore/FreeListInlines.h:46
#9 JSC::LocalAllocator::allocate (this=0x148296c38e30, heap=..., cellSize=16, deferralContext=0x0, failureMode=JSC::AllocationFailureMode::Assert) at cache/webkit-a73e665a39b281c5/include/JavaScriptCore/LocalAllocatorInlines.h:44
#10 JSC::GCClient::IsoSubspace::allocate (this=0x148296c38e30, vm=..., cellSize=16, deferralContext=0x0, failureMode=JSC::AllocationFailureMode::Assert) at cache/webkit-a73e665a39b281c5/include/JavaScriptCore/IsoSubspaceInlines.h:34
#11 JSC::tryAllocateCellHelper<JSC::JSString, (JSC::AllocationFailureMode)0> (vm=..., size=16, deferralContext=0x0) at cache/webkit-a73e665a39b281c5/include/JavaScriptCore/JSCellInlines.h:192
#12 JSC::allocateCell<JSC::JSString> (vm=..., size=16) at cache/webkit-a73e665a39b281c5/include/JavaScriptCore/JSCellInlines.h:212
#13 JSC::JSString::create (vm=..., value=...) at cache/webkit-a73e665a39b281c5/include/JavaScriptCore/JSString.h:204
#14 0x0000000004479ad1 in JSC::jsNontrivialString (vm=..., s=...) at vendor/WebKit/Source/JavaScriptCore/runtime/JSString.h:846
#15 JSC::VM::ensureTerminationException (this=0x148296c30000) at vendor/WebKit/Source/JavaScriptCore/runtime/VM.cpp:627
#16 JSGlobalObject__requestTermination (globalObject=<optimized out>) at ./build/release/./src/bun.js/bindings/ZigGlobalObject.cpp:3979
#17 0x0000000003405ab8 in bun.js.web_worker.notifyNeedTermination (this=0x542904f0d80) at /var/lib/buildkite-agent/builds/ip-172-31-16-28/bun/bun/src/bun.js/web_worker.zig:558
#18 0x0000000004362b6f in WebCore::Worker::terminate (this=0x984c900000000000) at ./src/bun.js/bindings/webcore/Worker.cpp:266
#19 WebCore::jsWorkerPrototypeFunction_terminateBody(JSC::JSGlobalObject*, JSC::CallFrame*, WebCore::JSWorker*)::{lambda()#1}::operator()() const (this=<optimized out>) at ./build/release/./src/bun.js/bindings/webcore/JSWorker.cpp:549
#20 WebCore::toJS<WebCore::IDLUndefined, WebCore::jsWorkerPrototypeFunction_terminateBody(JSC::JSGlobalObject*, JSC::CallFrame*, WebCore::JSWorker*)::{lambda()#1}>(JSC::JSGlobalObject&, JSC::ThrowScope&, WebCore::jsWorkerPrototypeFunction_terminateBody(JSC::JSGlobalObject*, JSC::CallFrame*, WebCore::JSWorker*)::{lambda()#1}&&) (lexicalGlobalObject=..., throwScope=..., valueOrFunctor=...) at ./src/bun.js/bindings/webcore/JSDOMConvertBase.h:174
#21 WebCore::jsWorkerPrototypeFunction_terminateBody (lexicalGlobalObject=<optimized out>, callFrame=<optimized out>, castedThis=<optimized out>) at ./build/release/./src/bun.js/bindings/webcore/JSWorker.cpp:549
#22 WebCore::IDLOperation<WebCore::JSWorker>::call<&WebCore::jsWorkerPrototypeFunction_terminateBody, (WebCore::CastedThisErrorBehavior)0> (lexicalGlobalObject=..., operationName=..., callFrame=...) at ./src/bun.js/bindings/webcore/JSDOMOperation.h:63
#23 WebCore::jsWorkerPrototypeFunction_terminate (lexicalGlobalObject=<optimized out>, callFrame=0x7ffe540536b8) at ./build/release/./src/bun.js/bindings/webcore/JSWorker.cpp:554
#24 0x000014825580c038 in ?? ()
#25 0x00007ffe540537b0 in ?? ()
#26 0x0000148255a626cb in ?? ()
#27 0x0000000000000000 in ?? ()
1 crashes reported during this test
```
pull Bot pushed a commit that referenced this pull request Apr 26, 2026
×ばつ), polling up to `250 ×ばつ 5ms = 1.25s` for the mount to appear. On Alpine, this file's deterministic shard slot happens to run **while `docker compose` is still extracting Redis/MinIO images** in the background. With disk I/O saturated, the first python3/libfuse cold-start exceeds the 1.25s budget and the assertion at line 41 fails. Tests 2-4 in the same file then pass (warm page cache, ~170ms per mount), and the retry passes (docker has finished). `run-file-on-fuse.test.ts` has the identical pattern but never flakes because it lands in a different shard whose tests #1-12 are slower, so it runs ~50s after docker finishes. | Shard | Test #13 starts | Docker compose finishes | Result | |---|---|---|---| | glob-on-fuse | t+136s | t+143s (7s **after**) | flake | | run-file-on-fuse | t+195s | t+143s (52s **before**) | pass | ### Fix - Mount once in `beforeAll` / unmount in `afterAll` instead of per-test (×ばつ×ばつ mount cycles). - Raise the poll budget from 1.25s to 8s; still exits early if the python process crashes. - `afterAll` runs even if `beforeAll` throws, so cleanup is guaranteed. - Applied the same change to `run-file-on-fuse.test.ts` since it has the same latent issue. ## How did you verify your code works? - `bun bd test test/cli/run/glob-on-fuse.test.ts test/cli/run/run-file-on-fuse.test.ts` → 6 pass, 0 fail - 20 consecutive runs of `glob-on-fuse.test.ts` and 10 of both files together → all pass, no leaked mounts - Passes under simulated cold-cache + I/O contention locally - Verified `afterAll` runs when `beforeAll` throws in Bun's test runner" data-pjax="true" href="/index.cgi/contrast/https://github.com/Mu-L/bun/commit/c94252478e9941f684b4826698314487f8ffb744">test: deflake FUSE tests by mounting once with a longer poll budget ( o...
...ven-sh#29718)
## What does this PR do?
Fixes the `glob-on-fuse.test.ts` flake on Alpine CI (79 occurrences
across 44 of the last 70 builds, e.g. [build
47922](https://buildkite.com/bun/bun/builds/47922)).
### Root cause
The test mounts a FUSE filesystem via `python3 fuse-fs.py` once **per
test** (×ばつ), polling up to `250 ×ばつ 5ms = 1.25s` for the mount to appear.
On Alpine, this file's deterministic shard slot happens to run **while
`docker compose` is still extracting Redis/MinIO images** in the
background. With disk I/O saturated, the first python3/libfuse
cold-start exceeds the 1.25s budget and the assertion at line 41 fails.
Tests 2-4 in the same file then pass (warm page cache, ~170ms per
mount), and the retry passes (docker has finished).
`run-file-on-fuse.test.ts` has the identical pattern but never flakes
because it lands in a different shard whose tests #1-12 are slower, so
it runs ~50s after docker finishes.
| Shard | Test #13 starts | Docker compose finishes | Result |
|---|---|---|---|
| glob-on-fuse | t+136s | t+143s (7s **after**) | flake |
| run-file-on-fuse | t+195s | t+143s (52s **before**) | pass |
### Fix
- Mount once in `beforeAll` / unmount in `afterAll` instead of per-test
(×ばつ×ばつ mount cycles).
- Raise the poll budget from 1.25s to 8s; still exits early if the
python process crashes.
- `afterAll` runs even if `beforeAll` throws, so cleanup is guaranteed.
- Applied the same change to `run-file-on-fuse.test.ts` since it has the
same latent issue.
## How did you verify your code works?
- `bun bd test test/cli/run/glob-on-fuse.test.ts
test/cli/run/run-file-on-fuse.test.ts` → 6 pass, 0 fail
- 20 consecutive runs of `glob-on-fuse.test.ts` and 10 of both files
together → all pass, no leaked mounts
- Passes under simulated cold-cache + I/O contention locally
- Verified `afterAll` runs when `beforeAll` throws in Bun's test runner
pull Bot pushed a commit that referenced this pull request May 4, 2026
×ばつ clean `--parallel=4` runs (was 3–4 crashes/run). - Gate: `git stash -- src/ && bun bd test test/regression/issue/30205.test.ts` → 3/4 fail; with fix → 4/4 pass. - `test/cli/test/isolation.test.ts`, `test/regression/issue/29519.test.ts` → pass (one pre-existing unrelated timeout in isolation.test.ts, same as #29573). - `test/cli/test/parallel.test.ts` → all tests I touched pass; the 3 timing-sensitive scale-up/work-steal tests that fail in this container fail identically on unmodified `main`. --------- Co-authored-by: robobun <robobun@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>" data-pjax="true" href="/index.cgi/contrast/https://github.com/Mu-L/bun/commit/f8fee8d825c6e85843915f904579bf669114e41b">test --isolate: retarget NapiEnv at new global; --parallel: abort on ...
...worker panic, never retry (oven-sh#30216)
## What
`bun test --isolate` / `--parallel` crashes when a test file loads a
native addon whose deferred napi finalizers outlive the file. The
`--parallel` coordinator then silently retries the file once, which
masks the panic and lets the run exit 0.
Fixes oven-sh#30205, oven-sh#30191. Supersedes oven-sh#30214 (same NapiEnv fix, but without
the coordinator change, the `cleanup_hooks` retarget, or a test that
actually reproduces on unpatched `main`).
## Reproduction
```sh
git clone https://github.com/workglow-dev/libs && cd libs
bun i && bun run build:packages
bun test --timeout=30000 --parallel=4 packages/test/src/test/{util,task}/*.test.ts
```
On `main` (d484fd6), 3–4 workers crash per run with either
```
ASSERTION FAILED: isMarked(cell)
 JavaScriptCore/heap/Heap.cpp:1232 : void JSC::Heap::addToRememberedSet(const JSCell *)
```
or (when the slot is already being reallocated)
```
ASSERTION FAILED: m_cellState == CellState::DefinitelyWhite
 JavaScriptCore/JSCellInlines.h:69 : JSC::JSCell::JSCell(VM &, Structure *)
```
and in release builds the segfaults at `0x68` / `0xD0` reported in
oven-sh#30205.
## Root cause
Frame-pointer walk from the assertion:
```
#3 Bun::NapiHandleScope::open(Zig::GlobalObject*, bool)
#4 NapiHandleScope__open
#6 napi.Finalizer.run
#7 napi.NapiFinalizerTask.runOnJSThread
#10 event_loop.tick
#11 event_loop.waitForPromise
#13 VirtualMachine.loadEntryPointForTestRunner ← next test file
```
`NapiEnv::m_globalObject` is a raw `Zig::GlobalObject*`. For
non-experimental addons (`nm_version != NAPI_VERSION_EXPERIMENTAL`,
which is ~every real-world addon — sharp, better-sqlite3, etc.),
`napi_wrap`/`napi_create_external` finalizers are **deferred** to the
event loop as `NapiFinalizerTask` rather than run inside GC sweep.
Objects rooted on the old global (module graph, `globalThis.*`) only
become collectable when `Zig__GlobalObject__createForTestIsolation` runs
`gcUnprotect(oldGlobal)`. The `DeferGC` from oven-sh#29573 ends at that
function's `}`, so the next GC runs there, collects those objects, and
enqueues their finalizers. Those tasks then run on the very next
`eventLoop().tick()` — inside `loadEntryPointForTestRunner`'s
`waitForPromise` for file N+1. `Finalizer.run` opens a `NapiHandleScope`
via `env->globalObject()`, which reads `NapiHandleScopeImplStructure()`
off the dead cell and writes `m_currentNapiHandleScopeImpl` on it →
write barrier on an unmarked cell.
The `--parallel` coordinator's `reapWorker` then re-queued the file once
(`retries[idx] < 1`) into a fresh worker with no stale `NapiEnv`, which
passed — so the run reported 0 fail despite multiple Bun panics in the
log.
## Fix
**NapiEnv retarget** (`ZigGlobalObject.cpp`, `napi.h`):
`Zig__GlobalObject__createForTestIsolation` now calls
`newGlobal->adoptNapiEnvsForTestIsolation(oldGlobal)` before
`gcUnprotect`. Each `NapiEnv::m_globalObject` is repointed at the new
global and the `Ref<NapiEnv>`s are moved over, so late finalizers open
handle scopes on a live global and the envs stay owned after the old
global is swept. `VirtualMachine.swapGlobalForTestIsolation` also
repoints `rare_data.cleanup_hooks[*].globalThis` so `CleanupHook.eql()`
stays accurate.
**No retry, abort on panic** (`Coordinator.zig`): removed the per-file
retry. A worker that dies mid-file is counted as one failure. If it died
by a fatal signal (SIGILL/SIGTRAP/SIGABRT/SIGBUS/SIGFPE/SIGSEGV/SIGSYS —
Bun's own `@trap()`, a JSC/WTF assertion, or native-addon crash), the
whole run aborts with `error: a test worker process crashed with <SIG>
while running <file>`. `process.exit()` / SIGKILL are still just a
per-file failure and the run continues.
## Verification
- `test/regression/issue/30205.test.ts` — 4 tests. Adds a tiny
non-experimental addon (`isolate_finalizer_addon.c`) and a fixture
pattern (`Bun.gc(true)` + module-scope `await 0` + objects rooted on
`globalThis`) that crashes **8/8** on unpatched `main` and passes 8/8
with this change.
- `workglow-dev/libs` full 201-file unit suite: ×ばつ clean `--parallel=4`
runs (was 3–4 crashes/run).
- Gate: `git stash -- src/ && bun bd test
test/regression/issue/30205.test.ts` → 3/4 fail; with fix → 4/4 pass.
- `test/cli/test/isolation.test.ts`,
`test/regression/issue/29519.test.ts` → pass (one pre-existing unrelated
timeout in isolation.test.ts, same as oven-sh#29573).
- `test/cli/test/parallel.test.ts` → all tests I touched pass; the 3
timing-sensitive scale-up/work-steal tests that fail in this container
fail identically on unmodified `main`.
---------
Co-authored-by: robobun <robobun@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
pull Bot pushed a commit that referenced this pull request Jun 23, 2026
...e re-enters the event loop (oven-sh#32597)
Sentry BUN-2WJA / BUN-2WKB (~290 events combined, Windows x86_64,
`http_server=True`, bun 1.2.23 through 1.3.14):
```
Segmentation fault at address 0xFFFFFFFFFFFFFFFF
 endWithSink src/runtime/webcore/Sink.zig:577
 endFromJS src/runtime/webcore/streams.zig:1200
 finalize src/runtime/webcore/streams.zig:1301
 clearAndFree src/collections/baby_list.zig:148
 memset (fault at 0xFFFFFFFFFFFFFFFF)
```
## Cause
The generated `JSReadable*Controller` `end()` and `close()` host
functions (`src/codegen/generate-jssink.ts`) stash `m_sinkPtr` in a
local, call `controller->detach()`, and only afterward dereference the
stashed pointer via `endWithSink()` / `${name}__close()`:
```cpp
void *ptr = controller->wrapped();
controller->detach(); // runs onClose JS synchronously
return ${name}__endWithSink(ptr, lexicalGlobalObject); // derefs ptr
```
`detach()` invokes the stored `onClose` callback. For a `type: "direct"`
stream this is `readDirectStream`'s `close(stream, reason)`, which calls
`underlyingSource.cancel()`. That is arbitrary user code running while
`ptr` is still live on the C++ stack.
If the stream's `pull()` promise has already settled,
`RequestContext::on_resolve_stream` is sitting in the microtask queue.
Any path from `cancel()` that drains microtasks (e.g. the server-side
drain points in `on_response` / `do_render_with_body`, or an explicit
`drainMicrotasks()`) runs `handle_resolve_stream`, which calls
`destroy_sink` and frees the `HTTPServerWritable`. `endWithSink(ptr)`
then enters `end_from_js` on the freed allocation; `finalize()` reads
garbage for `pooled_buffer` / `buffer.cap` / `buffer.ptr` and faults in
the `memset` the allocator's free-scrub path performs.
The same ordering appears in the Rust port (`streams.rs` / `Sink.rs`)
unchanged.
## Fix
In `${controller}__end` and `${controller}__close`, finish the native
sink operation before any JS runs:
1. Call `${name}__controllerDetached(ptr, controller)` and null
`m_sinkPtr` up front (so `end_from_js`'s own `signal.close()` stays a
no-op, matching the previous behaviour, and so the later `detach()`
won't touch the native side again).
2. Run `endWithSink(ptr)` / `close(ptr)`.
3. Call `controller->detach()` last. With `m_sinkPtr` already null it
only clears `m_onPull` and fires `onClose`; by now we hold no reference
into the sink, so re-entrant teardown is safe.
## Verification
New ASAN-gated test in
`test/js/bun/http/serve-direct-readable-stream.test.ts` reproduces the
exact UAF deterministically by draining microtasks from the stream's
`cancel()` callback (the test uses
`require("bun:jsc").drainMicrotasks()` to force the drain that the
production crash hits via the server's own drain points).
<details>
<summary>ASAN output on the unfixed build</summary>
```
==22203==ERROR: AddressSanitizer: heap-use-after-free on address 0x6ee5f87602ca
READ of size 1 at 0x6ee5f87602ca thread T0
 #0 HTTPServerWritable::end_from_js src/runtime/webcore/streams.rs:1831
 #2 JSSink::js_end_with_sink src/runtime/webcore/Sink.rs:1107
 #4 WebCore::JSReadableHTTPResponseSinkController__end JSSink.cpp:620
freed by thread T0 here:
 #10 HTTPServerWritable::destroy src/runtime/webcore/streams.rs:1950
 #11 RequestContext::destroy_sink src/runtime/server/RequestContext.rs:1930
 #12 RequestContext::handle_resolve_stream src/runtime/server/RequestContext.rs:2680
 #13 RequestContext::on_resolve_stream src/runtime/server/RequestContext.rs:2716
 ...
 #24 JSC::VM::drainMicrotasks()
```
</details>
With the fix the fixture completes normally. Existing suites
(`serve.test.ts`, `bun-server.test.ts`,
`direct-readable-stream.test.tsx`, `streams.test.js`, the sink leak
tests) show no new failures against the unfixed build.
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
pull Bot pushed a commit that referenced this pull request Jul 11, 2026
...hostname leak) (oven-sh#33901)
Fixes the flaky LSan failure in
`test/js/bun/http/proxy-stress-concurrent.test.ts` (\`memory probe >
abort-after-connect\`) on the x64-asan lane:
```
Direct leak of 9 byte(s) in 1 object(s) allocated from:
 ...
 #13 <alloc::boxed::Box<[u8]> as core::clone::Clone>::clone
 #14 <bun_runtime::dns_jsc::dns_body::Resolver>::get_or_put_into_pending_cache src/runtime/dns_jsc/dns.rs:4696
 #15 bun_runtime::dns_jsc::dns_body::lib_c::lookup src/runtime/dns_jsc/dns.rs:328
```
## Repro
Deterministic (10/10) with:
```sh
BUN_DESTRUCT_VM_ON_EXIT=1 ASAN_OPTIONS=detect_leaks=1 \
LSAN_OPTIONS=suppressions=test/leaksan.supp bun-debug -e '
 const net = require("net");
 const server = net.createServer(() => {});
 server.listen(0, "127.0.0.1", () => {
 const port = server.address().port;
 for (let i = 0; i < 20; i++) {
 const s = net.connect(port, "localhost");
 s.on("error", () => {});
 s.destroy();
 }
 process.exit(0);
 });'
```
## Cause
cd1ad59 added `name: Box<[u8]>` to the DNS pending-cache key so
hash+len collisions do not coalesce unrelated lookups. The key is
written into a `HiveArray` slot by `get_or_put_into_pending_cache` and
normally freed when the lookup completes (`drain_pending_host_native`
moves the key out and drops it).
Under `BUN_DESTRUCT_VM_ON_EXIT=1` (set by the CI runner for the ASAN
lane), `process.exit` runs `global_exit` -> `destroy()` ->
`deinit_runtime_state`, which drops the per-thread `RuntimeState` and
with it the global `Resolver`. If a libc `getaddrinfo` is still on the
work pool at that point, its pending-cache slot is still occupied.
`HiveArray` had no `Drop`, so the `[MaybeUninit<T>; N]` buffer was freed
without running `T::drop`, orphaning the 9-byte `Box<[u8]>` hostname.
The in-flight `GetAddrInfoRequest` itself stays reachable via the
re-queued task list on the static-rooted VM, so it was not reported;
only the hive-held hostname was.
## Fix
Give `HiveArray<T, CAP>` a `Drop` that `drop_in_place`s every
still-occupied slot (gated on `needs_drop::<T>()` so POD pools pay
nothing). The only other direct `HiveArray` user,
`HTTPContext::pending_sockets`, already neutralises each slot in its own
`Drop`, so the extra `drop_in_place` is a no-op there.
## Verification
- New ASAN-only test in `resolve-dns.test.ts` fails before the fix (LSan
reports the 9-byte leak, exit 1) and passes after (clean, exit 0).
- Repro script above: 10/10 leak before, 10/10 clean after.
- `cargo test -p bun_collections` (including a new Drop-counting
assertion) passes.
- `bun run rust:check-all` passes on all 10 targets.
<!-- robobun:evidence:begin -->
---
**no test proof** · iteration 1 · Platform-specific test(s) that do not
run on this machine. Deferring to CI, which covers all platforms:
test/js/bun/dns/resolve-dns.test.ts
<!-- robobun:evidence:end -->
pull Bot pushed a commit that referenced this pull request Jul 16, 2026
...ry rewrite (oven-sh#34271)
`test/js/bun/util/filesystem_router.test.ts` went red on alpine x64 in
build [73276](https://buildkite.com/bun/bun/builds/73276): the `reload()
while Bun.build() resolves the same directory` subprocess segfaulted in
`bust_dir_cache_recursive`, inlined from `NonNull::new`.
## Cause
`RealFS::entries_at` (`src/resolver/lib.rs`) replaces a cached
`DirEntry` in place when the caller's resolver generation is newer than
the cached listing's. The replacement at `*e_ptr = new_entry` drops the
old `DirEntry`, which drops its `data: StringHashMap<*mut Entry>` and
frees the hashmap's bucket allocation. The function's comment says
`entries_mutex held by caller`, but that is only true on one of the five
paths that reach it: `dir_info_uncached`, when entered from
`dir_info_cached_miss`. The other callers (`finalize_result`,
`handle_esm_resolution`, `load_index_with_extension`,
`Transpiler::run_env_loader`) all reach `entries_at` after
`dir_info_cached_maybe_log` has already returned and released both
`RESOLVER_MUTEX` and `entries_mutex`.
`FileSystemRouter::reload()` and `RouteLoader::load` iterate the same
`DirEntry.data` map under `entries_mutex` (the snapshot pattern oven-sh#33056
introduced for exactly this kind of concurrent rewrite). With
`entries_at`'s rewrite unsynchronized, a `Bun.build()` on the bundler
thread can drop the map while `reload()` on the JS thread is
mid-iteration.
The generation mismatch is what makes `entries_at` enter its rewrite
branch, so the window only opens once the bundle thread has processed at
least one batch (it bumps its own generation after every queue drain);
every subsequent `Bun.build()` then re-reads any directory that
`reload()` just refreshed to generation 0.
ASAN catches it as a heap-use-after-free with the two sides of the race
laid out exactly:
```
READ of size 16 (thread T0):
 #6 HashMap::values
 #7 StringHashMap<*mut Entry>::values src/collections/array_hash_map.rs:1864
 #8 FileSystemRouter::bust_dir_cache_recursive src/runtime/api/filesystem_router.rs:395
 #9 FileSystemRouter::bust_dir_cache src/runtime/api/filesystem_router.rs:451
 #10 FileSystemRouter::reload src/runtime/api/filesystem_router.rs:476
freed by thread T11 (Bundler):
 #11 drop_in_place<bun_resolver::fs_full::DirEntry>
 #12 bun_resolver::fs::RealFS::entries_at src/resolver/lib.rs:1639
 #13 DirInfo::get_entries_ref src/resolver/dir_info.rs:266
 #14 Resolver::finalize_result src/resolver/resolver.rs:1714
 #15 Resolver::resolve_and_auto_install src/resolver/resolver.rs:1485
 ...
 #23 BundleThread::generate_in_new_thread src/bundler/BundleThread.rs:276
previously allocated by thread T0:
 #17 HashMap::reserve
 #18 Resolver::dir_info_cached_miss src/resolver/resolver.rs:4591
 #19 Resolver::dir_info_cached_maybe_log src/resolver/resolver.rs:4201
 #20 Resolver::read_dir_info src/resolver/resolver.rs:4118
 #21 FileSystemRouter::reload src/runtime/api/filesystem_router.rs:492
```
(The use side is sometimes `RouteLoader::load` at
`src/router/lib.rs:816` instead; same map, same lock.)
This has been the shape of `entries_at` since the Rust port; oven-sh#33056
narrowed the race by snapshotting under the lock but assumed the rewrite
side already held it.
## Fix
`entries_at` now takes `entries_mutex` itself, matching
`read_directory_with_iterator` which already does. The one call path
that reaches it with the lock already held (`dir_info_cached_miss` ->
`dir_info_uncached` -> `parent_.get_entries_ref`) routes through a new
`entries_at_locked` / `get_entries_ref_locked` pair so the non-recursive
mutex is not re-entered. That path is the only one that passes a
non-`None` parent to `dir_info_uncached`; the other caller
(`dir_info_for_resolution`) passes `None`, so the parent branch
containing the accessor never runs there.
## Test
The existing concurrency test now awaits one `Bun.build()` first, so the
bundle thread's generation is already past zero when the concurrent
rounds start, and then runs forty reload/build rounds instead of one.
That is the shape that reaches the stale-generation rewrite at all; the
original single-round fixture usually completes with every build still
on generation 0.
The race is scheduling-dependent. Pinning the fixture to a single core
reproduces the ASAN use-after-free on roughly 3 in 10 runs against an
unpatched debug build and 0 in 15 with this change; with all 16 cores
available the unpatched build reproduces at roughly 1 in 30. The
assertions are otherwise the same as before, so the test continues to
cover the behavior oven-sh#33056 added.
Also ran the full `filesystem_router.test.ts`,
`test/bundler/bun-build-api.test.ts` (including the thousands-of-builds
test that exercises the generation path heavily),
`test/js/bun/resolve/resolve.test.ts`, `test/cli/hot/hot.test.ts`,
`test/cli/watch/watch.test.ts`, `test/bake/framework-router.test.ts`,
and `bun run rust:check-all` (10/10 targets).
<!-- robobun:evidence:begin -->
---
**no test proof** · iteration 0 · Platform-specific test(s) that do not
run on this machine. Deferring to CI, which covers all platforms:
test/js/bun/util/filesystem_router.test.ts
<!-- robobun:evidence:end -->
pull Bot pushed a commit that referenced this pull request Jul 16, 2026
...e drain (oven-sh#34278)
## Problem
`test/js/node/test/parallel/test-worker-stdio-flush.js` went red on the
`debian 13 x64-asan` lane of [build
73374](https://buildkite.com/bun/bun/builds/73374) with:
```
==18202==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 32 byte(s) in 1 object(s) allocated from:
 #9 ConcurrentTask::new src/event_loop/ConcurrentTask.rs:305
 #10 ConcurrentTask::create src/event_loop/ConcurrentTask.rs:319
 #12 bun_jsc::virtual_machine_exports::queue_task_concurrently src/jsc/virtual_machine_exports.rs:140
 #13 ScriptExecutionContext::postTaskConcurrently src/jsc/bindings/ScriptExecutionContext.cpp:266
 #14 ScriptExecutionContext::postTaskTo src/jsc/bindings/ScriptExecutionContext.cpp:125
 #15 MessagePortPipe::scheduleDrain src/jsc/bindings/webcore/MessagePortPipe.cpp:74
 #16 MessagePort::postMessage src/jsc/bindings/webcore/MessagePort.cpp:143
```
The leaked allocation is a `ConcurrentTask` (and the `EventLoopTask` it
wraps) left in an exiting worker's `concurrent_tasks` queue after the
queue has been drained for the last time.
## Cause
`WebWorker::shutdown()` runs `process.on('exit')` handlers, then drains
the worker's concurrent queue via `release_queued_tasks_for_shutdown()`,
then enters `WebWorker__teardownJSCVM` which (first thing) calls
`ctx->markTerminating()`. `ScriptExecutionContext::postTaskTo` already
refuses to enqueue onto a terminating context, but between the drain and
the flag flip there is a short window where a cross-thread poster still
sees `isTerminating() == false` and enqueues.
In the failing test the worker writes to `process.stdout` inside its
`exit` handler. The parent's captured-stdout reader acks each chunk with
`port.postMessage(true)` (`src/js/node/worker_threads.ts`
`makePortReadable._read`), which routes through
`MessagePortPipe::scheduleDrain` to `postTaskTo(workerCtxId, ...)`. When
the ack lands in that window it is pushed onto the worker's
`concurrent_tasks`; nothing drains it again, and the worker's VM box is
`dealloc`'d raw, so LSan reports the `ConcurrentTask` as a direct leak.
The window is a few assignments plus one FFI call wide, so it hits
probabilistically; the `release-asan` build is fast enough to line up
occasionally, debug essentially never.
The ordering was introduced in oven-sh#31216; oven-sh#29917 described the same gap ("a
task posted between this drain and `removeFromContextsMap()` inside
`teardownJSCVM` still leaks") but left it open.
## Fix
- `ScriptExecutionContext::markTerminating()` now takes
`allScriptExecutionContextsMapLock`, the same lock `postTaskTo` holds
across its `isTerminating()` check and `postTaskConcurrently()` enqueue.
That makes the flag flip a proper fence against concurrent posters: any
`postTaskTo` critical section either runs entirely before
`markTerminating()` (its task is visible to the subsequent drain) or
entirely after (it observes `true` and drops).
- `WebWorker::shutdown()` calls the new `extern "C"
ScriptExecutionContext__markTerminating` immediately before
`release_queued_tasks_for_shutdown()`, closing the window. The later
`markTerminating()` inside `WebWorker__teardownJSCVM` is now redundant
but harmless.
No behaviour change for `process.on('exit')` itself: that runs before
the new call, so a parent ack posted while the handler is running is
still enqueued and then freed by the drain (never executed, same as
before). Only posts that would have landed after the drain are now
dropped instead of leaked.
## Verification
The gap is too narrow to reproduce unassisted against a debug build: 200
iterations of the Node test with the CI LSan env, and 150 worker
shutdowns with 64 Atomics-synchronized MessagePorts each, all pass on an
unpatched `bun bd`. Widening the gap with a temporary
`std::thread::sleep(5ms)` between `release_queued_tasks_for_shutdown()`
and `WebWorker__teardownJSCVM` makes it deterministic:
| build | `test-worker-stdio-flush.js` under LSan | 200-port
Atomics-synchronized probe |
| --- | --- | --- |
| unpatched + 5 ms sleep | 5/5 leak (`32 byte(s) ConcurrentTask`) | 5/5
leak |
| this PR + 5 ms sleep | 10/10 clean | 5/5 clean |
| this PR (no sleep) | 50/50 clean | clean |
`test/js/node/worker_threads/worker-shutdown-post-leak.test.ts` runs the
worker-stdio-on-exit scenario under `detect_leaks=1` as an ASAN-lane
guard (in a fresh file so it actually runs; `worker_destruction.test.ts`
is ASAN-quarantined via `test/expectations.txt`). The race is not
observable on the debug gate without `src/` instrumentation, so the
fail-before half will not fire there; `test-worker-stdio-flush.js` on
the release-asan lane remains the primary signal.
Related: oven-sh#31216 (introduced the ordering), oven-sh#29917 (described but left
the remaining window).
<!-- robobun:evidence:begin -->
---
**no test proof** · iteration 1 · Platform-specific test(s) that do not
run on this machine. Deferring to CI, which covers all platforms:
test/js/node/worker_threads/worker-shutdown-post-leak.test.ts
<!-- robobun:evidence:end -->
pull Bot pushed a commit that referenced this pull request Jul 29, 2026
...ed (oven-sh#36247)
## What
`test/js/bun/http/bun-serve-html.test.ts` segfaults on `windows-aarch64`
after oven-sh#36175 landed (builds 84162, 84194; one earlier sighting in
83933):
```
panic(main thread): Segmentation fault at address 0x48
Features: ... dev_server(14) ...
```
Symbolicated in oven-sh#36214 as `AsyncFSTask<Access>::run_from_js_thread` with
`self = null`, i.e. a zeroed `ConcurrentTask` was dispatched.
## Cause
`DevServer.watcher_atomics.events[*].concurrent_task` is the intrusive
MPSC node the watcher thread links into `EventLoop.concurrent_tasks`
when it submits a hot-reload event. It was an inline field of
`DevServer`, so `server.stop()` → `drop(Box<DevServer>)` freed it while
it was still linked. The next `tick_concurrent` then read
`.next`/`.task`/`.auto_delete` from freed memory. ASAN on Linux
confirms:
```
heap-use-after-free: ConcurrentTask::get_next (unbounded_queue.rs)
 ← BatchIterator::next ← EventLoop::tick_concurrent_with_count
freed by: Box<DevServer>::drop ← NewServer::deinit_if_we_can
 ← NewServer::stop ← dispose_from_js (using server)
```
On release builds the freed block reads back as zeros, so the copied
`Task` is `{tag: 0, ptr: null}`; tag 0 is `task_tag::Access`, whose
`run_from_js_thread` loads `self.result` at offset `0x48`.
The bug is latent and platform-agnostic. oven-sh#36175 exposed it because the
CI runner now spawns the napi addon prebuild in the background while
serial tests run; that writes under the watched project root, so the
`jsx-runtime` DevServers in this test file now reliably receive a
hot-reload event between the last `await fetch` and `using server`
disposal.
## Fix
`watcher_atomics` is now a `NonNull<WatcherAtomics>` owned via
`bun_core::heap::into_raw`, so the allocation can outlive `DevServer`
and every queued pointer keeps allocation-root provenance.
`watcher_acquire_event`, `watcher_release_and_submit_event` and
`recycle_event_from_dev_server` take `*mut Self` and derive the returned
`*mut HotReloadEvent` (and the linked `concurrent_task` node) from that
root pointer via raw place projections rather than from a `&mut
WatcherAtomics` reborrow.
`Drop for DevServer` reads `next_event` after `Watcher::shutdown` has
serialised out the watcher thread (which guarantees it is stable):
- `DONE`: nothing is queued; clear and `heap::destroy` as before.
- otherwise: a `concurrent_task` is still linked (or its `Task` is
already in the drain FIFO). Null `owner` on every event and leave the
allocation alive.
`HotReloadEvent::run` checks `owner.is_null()` first; when set it
reclaims the allocation via the new `atomics` backref and returns
without touching the dead `DevServer`. The `# Safety` contracts on `run`
and the `BakeHotReloadEvent` dispatch arm are updated to describe the
null-owner case.
## Test
`test/js/bun/http/bun-serve-html-hot-reload-drop.test.ts` creates a
development server, bundles once so `app.js` is watched, synchronously
rewrites `app.js`, spins briefly without yielding so the watcher thread
can enqueue, disposes the server, then yields. Ten iterations. In a
separate file because the React-bundling cases in
`bun-serve-html.test.ts` already exceed the default per-test timeout
under a debug+ASAN build on `main`.
<details><summary>fail-before (debug+ASAN, src/ at main)</summary>
```
==25521==ERROR: AddressSanitizer: heap-use-after-free on address 0x79315e4743e8
READ of size 8 at 0x79315e4743e8 thread T0
 #2 <ConcurrentTask as Node>::get_next unbounded_queue.rs:82
 #3 BatchIterator<ConcurrentTask>::next unbounded_queue.rs:135
 #4 EventLoop::tick_concurrent_with_count event_loop.rs:507
0x79315e4743e8 is located 488 bytes inside of 16512-byte region
freed by thread T0 here:
 #9 Box<DevServer>::drop
 #12 NewServer<false,true>::deinit_if_we_can mod.rs:1770
 #13 NewServer<false,true>::stop mod.rs:1665
 #14 NewServer<false,true>::dispose_from_js server_body.rs:2584
```
</details>
Passes with the fix in ~2.4s under debug+ASAN (also on a local
`windows-aarch64` debug build, where the original
`bun-serve-html.test.ts` is now 19/19);
`test/bake/deinitialization.test.ts` still green.
Supersedes the producer half of oven-sh#36214 (which adds a sentinel for the
same zeroed-task symptom).
<!-- robobun:evidence:begin -->
---
**[review]** gate passed · iteration 4 · 6 files touched
<details><summary>fails on main (without fix)</summary>
```console
ASAN without fix: 1 failed, 2 skipped
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/bun/http/bun-serve-html.test.ts test/js/bun/http/bun-serve-html-hot-reload-drop.test.ts
bun test v1.4.0 (5f6622f)
test/js/bun/http/bun-serve-html.test.ts:
waitForServer /tmp/html-css-js_ObkyZk {
 "/": "/tmp/html-css-js_ObkyZk/index.html",
 "/dashboard": "/tmp/html-css-js_ObkyZk/dashboard.html",
}
[0.12ms] bundle index.html 1.09 KB
[0.05ms] bundle dashboard.html 1.27 KB
(pass) serve html [630.67ms]
waitForServer /tmp/bun-serve-html-txt_5C6B7a {
 "/": "/tmp/bun-serve-html-txt_5C6B7a/index.html",
}
[0.15ms] bundle index.html 0.40 KB
HASH efbnbska
(pass) serve plugins > basic plugin [556.20ms]
waitForServer /tmp/html-css-js-failing-plugin_OPRhwb {
 "/": "/tmp/html-css-js-failing-plugin_OPRhwb/index.html",
}
error: Plugin failed intentionally
 at /tmp/html-css-js-failing-plugin_OPRhwb/styles.css:0
error: Plugin failed intentionally
 at /tmp/html-css-js-failing-plugin_OPRhwb/styles.css:0
(pass) serve plugins > serve html with failing plugin [491.35ms]
waitForServer /tmp/html-css-js-empty-plugins_biqnN6 {
 "/": "/tmp/htm
... (truncated)
release without fix: all passed
bun test v1.4.0-canary.1 (96ff7ec)
test/js/bun/http/bun-serve-html.test.ts:
waitForServer /tmp/html-css-js_ZmgkBG {
 "/": "/tmp/html-css-js_ZmgkBG/index.html",
 "/dashboard": "/tmp/html-css-js_ZmgkBG/dashboard.html",
}
[0.00ms] bundle index.html 1.09 KB
[0.00ms] bundle dashboard.html 1.27 KB
(pass) serve html [25.17ms]
waitForServer /tmp/bun-serve-html-txt_uWNogT {
 "/": "/tmp/bun-serve-html-txt_uWNogT/index.html",
}
[0.00ms] bundle index.html 0.40 KB
HASH efbnbska
(pass) serve plugins > basic plugin [17.25ms]
waitForServer /tmp/html-css-js-failing-plugin_Kd1a8p {
 "/": "/tmp/html-css-js-failing-plugin_Kd1a8p/index.html",
}
error: Plugin failed intentionally
 at /tmp/html-css-js-failing-plugin_Kd1a8p/styles.css:0
error: Plugin failed intentionally
 at /tmp/html-css-js-failing-plugin_Kd1a8p/styles.css:0
(pass) serve plugins > serve html with failing plugin [16.33ms]
waitForServer /tmp/html-css-js-empty-plugins_Ecz7qJ {
 "/": "/tmp/html-css-js-empty-plugins_Ecz7qJ/index.html",
}
[0.00ms] bundle index.html 0.71 KB
(pass) serve plugins > empty plugin array [13.23ms]
Waiting for server
waitForServer /tmp/html-css-js-concurrent-plugins_l7wQg5 {
 "/": "/tmp/
... (truncated)
```
</details>
<details><summary>passes on PR (with fix)</summary>
```console
ASAN with fix: 2 skipped
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/bun/http/bun-serve-html.test.ts test/js/bun/http/bun-serve-html-hot-reload-drop.test.ts
bun test v1.4.0 (5f6622f)
test/js/bun/http/bun-serve-html.test.ts:
waitForServer /tmp/html-css-js_CpXhx4 {
 "/": "/tmp/html-css-js_CpXhx4/index.html",
 "/dashboard": "/tmp/html-css-js_CpXhx4/dashboard.html",
}
[0.09ms] bundle index.html 1.09 KB
[0.05ms] bundle dashboard.html 1.27 KB
(pass) serve html [588.64ms]
waitForServer /tmp/bun-serve-html-txt_rjZL4e {
 "/": "/tmp/bun-serve-html-txt_rjZL4e/index.html",
}
[0.15ms] bundle index.html 0.40 KB
HASH efbnbska
(pass) serve plugins > basic plugin [552.11ms]
waitForServer /tmp/html-css-js-failing-plugin_D8NJ2O {
 "/": "/tmp/html-css-js-failing-plugin_D8NJ2O/index.html",
}
error: Plugin failed intentionally
 at /tmp/html-css-js-failing-plugin_D8NJ2O/styles.css:0
error: Plugin failed intentionally
 at /tmp/html-css-js-failing-plugin_D8NJ2O/styles.css:0
(pass) serve plugins > serve html with failing plugin [505.55ms]
waitForServer /tmp/html-css-js-empty-plugins_vK0DVI {
 "/": "/tmp/htm
... (truncated)
release with fix: all passed
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped) in 673ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[1/7] gen bake.{client,server,error}.js
-> bake.client.js, bake.server.js, bake.error.js
[2/7] gen generated_host_exports.rs
generated_host_exports.rs: 94 exports (host=3, lazy=10, generic=81, rust=0); 240 extern-C blocks audited
[2/7] cargo bun_bin → libbun_rust.a (--target x86_64-unknown-linux-gnu)
 nightly-2026年07月20日-x86_64-unknown-linux-gnu unchanged - rustc 1.99.0-nightly (9f36de775 2026年07月19日)
�[1m�[92m Compiling�[0m bun_core v0.0.0 (/workspace/bun/src/bun_core)
�[1m�[92m Compiling�[0m bun_errno v0.0.0 (/workspace/bun/src/errno)
�[1m�[92m Compiling�[0m bun_ptr v0.0.0 (/workspace/bun/src/ptr)
�[1m�[92m Compiling�[0m bun_boringssl_sys v0.0.0 (/workspace/bun/src/boringssl_sys)
�[1m�[92m Compiling�[0m bun_safety v0.0.0 (/workspace/bun/src/safety)
�[1m�[92m Compiling�[0m bun_zlib_sys v0.0.0 (/workspace/bun/src/zlib_sys)
�[1m�[92m Compiling�[0m bun_cares_sys v0.0.0 (/workspace/bun/src/cares_sys)
�[1m�[92m Compiling�[0m bun_zstd v0.0.0 (/workspace/bun/src/zstd)
�[1m�[92m Compiling�[0m
... (truncated)
```
</details>
<details><summary>diff hotspot</summary>
```
src/runtime/bake/DevServer.rs | 63 +++-
 src/runtime/bake/dev_server/lifecycle.rs | 12 +-
 src/runtime/bake/dev_server/mod.rs | 401 +++++++++++----------
 src/runtime/dispatch.rs | 10 +-
 .../http/bun-serve-html-hot-reload-drop.test.ts | 82 +++++
 test/js/bun/http/bun-serve-html.test.ts | 10 +-
 6 files changed, 374 insertions(+), 204 deletions(-)
```
</details>
**gate history** · 3 passed · 2 rejected · iteration 4
<details><summary>evidence per changed file</summary>
```
file reads edits tests
src/runtime/bake/DevServer.rs 10 13 0
src/runtime/bake/dev_server/lifecycle.rs 5 9 0
src/runtime/bake/dev_server/mod.rs 13 12 0
src/runtime/dispatch.rs 3 2 0
test/js/bun/http/bun-serve-html-hot-reload-drop.test.ts 1 5 0
test/js/bun/http/bun-serve-html.test.ts 6 9 0
```
</details>
<!-- robobun:evidence:end -->
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
pull Bot pushed a commit that referenced this pull request Aug 2, 2026
...during VM shutdown (oven-sh#36750)
Fixes `test/js/bun/http/bun-serve-html-405.test.ts` going red on
x64-asan (build [87498](https://buildkite.com/bun/bun/builds/87498) and
several unrelated PR builds since ~87000).
## Repro
```
BUN_DESTRUCT_VM_ON_EXIT=1 ASAN_OPTIONS=detect_leaks=1 \
LSAN_OPTIONS=suppressions=test/leaksan.supp \
bun-debug test test/js/bun/http/bun-serve-html-405.test.ts
```
```
Indirect leak of 2104 byte(s) in 1 object(s) allocated from:
 ...
 #11 new<bun_runtime::server::NewServer<false, true>>
 #12 init<false, true> src/runtime/server/mod.rs:2009:47
 #13 bun_runtime::api::bun_object::serve src/runtime/api/BunObject.rs:1564:26
 ...
 #21 BunObject_callback_serve src/runtime/api/BunObject.rs:230:25
SUMMARY: AddressSanitizer: 2942 byte(s) leaked in 7 allocation(s).
```
10/10 without this change, 0/10 with it (local debug+ASAN).
## Cause
`using server = Bun.serve({ development: true, routes: { "/": html } })`
disposes via `stop(true)`, which makes `deinit_if_we_can()` downgrade
`js_value` to `Weak` and return. The `NewServer` Box is only freed once
the JS wrapper's `finalize()` fires and `schedule_deinit()` enqueues the
actual `deinit()` as a `ManagedTask`.
When the wrapper survives to `lastChanceToFinalize`
(`BUN_DESTRUCT_VM_ON_EXIT=1`, which the CI runner sets on ASAN lanes),
`global_exit()` has already had its last event-loop tick. The enqueued
task never runs, and `EventLoop::deinit()` drops the task box without a
cleanup (`ManagedTask::new` sets `cleanup: None`). The 2104-byte
`NewServer<false, true>` Box, its `config.static_routes` Vec, the
`html_bundle::Route` it refcounts, and the route's path strings are all
orphaned. `Route.server: Cell<Option<AnyServer>>` points back at the
server so LSan sees a pointer cycle and reports every allocation as
indirect.
The path has always existed, but before oven-sh#35356 the per-tick GC sampler
usually collected the wrapper during the handful of event-loop ticks
between the test body and `global_exit()`, so `schedule_deinit()` ran
while the loop was still live. With only the 1s idle-timer GC, a single
fast test like this one reaches shutdown with the wrapper still alive
more often (about half the PR builds since the merge).
## Fix
`schedule_deinit()` now sets `DEINIT_SCHEDULED` and returns without
enqueueing when `is_shutting_down()`. `finalize()` then frees the Box
synchronously when the server has been fully drained: it unboxes via
`Box::into_raw` first so the dealloc goes through the raw owning pointer
rather than a `&mut self` frame (whose FnEntry protector would make the
dealloc Stacked-Borrows UB, same pattern as `Listener::finalize` /
`UDPSocket::finalize`). Every JSC handle on the Drop chain
(`JSPromiseStrong`, `JsRef`, `UserRouteBuilder.callback: Strong`)
funnels through `Strong::Impl::destroy`, which is a no-op past
`is_shutting_down()`, so freeing here is safe.
The inline free is gated on `TERMINATED`: `NewApp::destroy` runs
`us_socket_group_deinit`, which unlinks the socket group from the loop's
list without closing any sockets still in it. A graceful `stop()` only
closes the listener and leaves keep-alive sockets open in the group;
destroying the app there would orphan them (seen as a 280-byte
`us_poll_t` direct leak on
`vendor/elysia/test/core/before-handle-arrow.test.ts` with an earlier
revision of this PR, and a `US_ASSERT(head_sockets==NULL)` abort on the
debug build). `TERMINATED` is set only once `app.close()` has run, so
the inline free is taken for abruptly-stopped servers (what `using
server` does) and skipped for gracefully-stopped ones, which is
identical to `main`'s behaviour for them.
Other callers that can reach `schedule_deinit()` past shutdown (a last
request draining inside `close_all_socket_groups`, which runs before
`lastChanceToFinalize`) only set the flag and leave the Box, since
`NewApp::destroy` there would delete the uws socket group mid-iteration.
## Verification
Two ASAN-only subprocess tests added:
- abrupt `stop(true)` of a dev server with an HTML route under
`BUN_DESTRUCT_VM_ON_EXIT=1` + `detect_leaks=1`: fails on `main` with the
7-allocation LSan report above; passes with this change.
- graceful `stop()` of a plain server with a keep-alive client
connection: passes on both `main` and this change (asserts
`us_socket_group_deinit`'s `head_sockets==NULL` precondition, which an
earlier revision of this change violated).
<!-- robobun:evidence:begin -->
---
**no test proof** · iteration 2 · Platform-specific test(s) that do not
run on this machine. Deferring to CI, which covers all platforms:
test/js/bun/http/bun-serve-html-405.test.ts
<!-- robobun:evidence:end -->
pull Bot pushed a commit that referenced this pull request Aug 16, 2026
...wo parallel vecs (oven-sh#39145)
### Problem
- `LOLHTMLContext` in `src/runtime/api/html_rewriter.rs` keeps two vecs,
`selectors` and `element_handlers`, that describe one thing: entry `i`
of each is the selector and the handler object from the same
`rewriter.on(selector, handlers)` call.
- The pairing is only held up by convention: `on_()` pushes to both,
`build_settings()` zips them back together, and a doc comment plus an
invariant comment explain it. The mordant `parallel_vecs` lint flags
this (the one baselined finding for this file).
### Fix
- Add `ElementHandlerEntry { selector, handler: Box<ElementHandler> }`
and store `element_handlers: Vec<ElementHandlerEntry>`. One vec, one
push in `on_()`, and `build_settings()` destructures each entry instead
of zipping.
- No behavior change: the same values are pushed in the same order, the
handler is still boxed (the lol-html closures built in
`build_settings()` hold raw pointers into the box, so it must not move
when the vec reallocates), and the body of the `build_settings()` loop
is unchanged. The `#[expect(clippy::vec_box)]` comes off
`element_handlers` because it is no longer a `Vec<Box<_>>`;
`document_handlers` keeps its own.
- Remove the `parallel_vecs:src/runtime/api/html_rewriter.rs` line from
`mordant-baseline.toml`.
- Tests, in `test/js/workerd/html-rewriter.test.js` (`on()
registrations`), pin down the two things this storage has to get right.
They pass before and after this change, since it is a refactor:
- Many selectors registered on one rewriter, with two rejected `on()`
calls in the middle, each still run the handlers they were registered
with, on two transforms of the same rewriter.
- `on()` called from inside a handler, often enough to reallocate the
registry while lol-html is still calling the handlers registered before
the transform started: the running transform is unaffected and the next
one picks the additions up. With the `Box` removed from
`ElementHandlerEntry` this test fails under ASAN with a
heap-use-after-free (report in the details below), so the boxing is now
covered rather than only commented.
- Verified:
- `bun bd test` on `test/js/workerd/html-rewriter.test.js` (165 tests,
including the new ones), `html-rewriter-end-error.test.ts`,
`html-rewriter-leak.test.ts`,
`test/js/web/html/html-rewriter-doctype.test.ts` and the HTMLRewriter
regression tests: all pass.
 - `cargo clippy -p bun_runtime --no-deps`: clean.
- `cargo dylint --all -p bun_runtime` with this baseline: nothing over
the baseline. The same command with the baseline line removed but the
source change stashed reports exactly the one `parallel_vecs` finding
for this file, so the removed line is the one this change fixes.
- Regenerating the baseline with `MORDANT_BASELINE_WRITE=1` also drops
two entries this PR does not touch
(`always_unwrapped_option:src/install/PackageInstall.rs`,
`narrowed_two_ways:src/runtime/node/node_crypto_binding.rs`); those
findings were already fixed on main by other changes and are left for a
separate cleanup.
### Background
- `HTMLRewriter.on(selector, handlers)` parses the CSS selector with
lol-html and wraps the JS handler object in an `ElementHandler` (the
protected `element`/`comments`/`text` callbacks). Nothing is handed to
lol-html at that point; registrations are collected in `LOLHTMLContext`,
which is shared by the rewriter and every transform it starts, because
`transform()` can run more than once.
- `build_settings()` runs at transform time and turns each registration
into a `(selector, ElementContentHandlers)` pair for lol-html. Its
closures capture a `NonNull<ElementHandler>` pointing into the heap
allocation owned by the `Box`, which is why the handler has to stay
boxed even though clippy would normally suggest otherwise. An `on()`
call after a transform has started (for example from inside a handler)
pushes onto the same vec, which is what makes the reallocation case
reachable from JS.
- `mordant-baseline.toml` is the ratchet for the mordant lint pack run
by the Rust lints workflow: it records the accepted number of findings
per (lint, file), and CI reports anything above those counts. Removing
the line here means a reintroduction of the pattern in this file would
be reported.
<details>
<summary>ASAN report from the new test with the Box removed from
ElementHandlerEntry</summary>
```
ERROR: AddressSanitizer: heap-use-after-free
READ of size 8
 #3 <ElementHandler as HandlerLike>::global src/runtime/api/html_rewriter.rs
 #4 handler_callback::<ElementHandler, Element, ...> src/runtime/api/html_rewriter.rs
 #5 ElementHandler::on_element src/runtime/api/html_rewriter.rs
 #6 build_settings::{closure#0} src/runtime/api/html_rewriter.rs
 #8 lol_html ContentHandlersDispatcher::handle_start_tag
freed by thread T0 here:
 #13 RawVec<ElementHandlerEntry>::grow_one
 #15 Vec<ElementHandlerEntry>::push
 #16 HTMLRewriter::on_ src/runtime/api/html_rewriter.rs
```
</details>
<!-- robobun:evidence:begin -->
---
**no test proof** · iteration 2 · Platform-specific test(s) that do not
run on this machine. Deferring to CI, which covers all platforms:
test/js/workerd/html-rewriter.test.js
<!-- robobun:evidence:end -->
---------
Co-authored-by: Alistair Smith <hi@alistair.sh>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

No reviews

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

1 participant

AltStyle によって変換されたページ (->オリジナル) /