From 7b72aa5030c9446acf3737654a25c48963f2a75f Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Tue, 18 Nov 2025 00:39:43 -0800 Subject: [PATCH 1/6] close threadlocal loop --- src/bun.js/web_worker.zig | 2 ++ src/deps/libuv.zig | 13 ++++--------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/bun.js/web_worker.zig b/src/bun.js/web_worker.zig index 4fb209e83253..173e57fa03f5 100644 --- a/src/bun.js/web_worker.zig +++ b/src/bun.js/web_worker.zig @@ -622,6 +622,8 @@ pub fn exitAndDeinit(this: *WebWorker) noreturn { arena_.deinit(); } + bun.windows.libuv.Loop.closeIfExists(); + bun.exitThread(); } diff --git a/src/deps/libuv.zig b/src/deps/libuv.zig index f09aa3c0c261..443360f540d6 100644 --- a/src/deps/libuv.zig +++ b/src/deps/libuv.zig @@ -658,15 +658,10 @@ pub const Loop = extern struct { _ = uv_loop_close(ptr); } - pub fn new() ?*Loop { - const ptr = bun.default_allocator.create(Loop) catch return null; - if (init(ptr) != null) return null; - return ptr; - } - - pub fn delete(ptr: *Loop) void { - close(ptr); - bun.default_allocator.destroy(ptr); + pub fn closeIfExists() void { + if (threadlocal_loop) |loop| { + loop.close(); + } } threadlocal var threadlocal_loop_data: Loop = undefined; From 51717ff20dd3b775dc0bf5d583b7cb99f09ca138 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Tue, 18 Nov 2025 14:01:33 -0800 Subject: [PATCH 2/6] update --- src/bun.js/web_worker.zig | 2 +- src/deps/libuv.zig | 27 +++++++++++++++++++++------ 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/bun.js/web_worker.zig b/src/bun.js/web_worker.zig index 173e57fa03f5..d1e73f3301e7 100644 --- a/src/bun.js/web_worker.zig +++ b/src/bun.js/web_worker.zig @@ -622,7 +622,7 @@ pub fn exitAndDeinit(this: *WebWorker) noreturn { arena_.deinit(); } - bun.windows.libuv.Loop.closeIfExists(); + bun.windows.libuv.Loop.close(); bun.exitThread(); } diff --git a/src/deps/libuv.zig b/src/deps/libuv.zig index 443360f540d6..c9d349beae73 100644 --- a/src/deps/libuv.zig +++ b/src/deps/libuv.zig @@ -654,14 +654,29 @@ pub const Loop = extern struct { return null; } - pub fn close(ptr: *Loop) void { - _ = uv_loop_close(ptr); + fn closeWalkCb(handle: ?*uv_handle_t, data: ?*anyopaque) callconv(.c) void { + _ = data; + const h = handle.?; + if (uv_is_closing(h) == 0) { + uv_close(h, null); + } } - pub fn closeIfExists() void { - if (threadlocal_loop) |loop| { - loop.close(); + pub fn close() void { + const loop = threadlocal_loop orelse { + return; + }; + + if (uv_loop_close(loop).errEnum()) |err| { + if (err == .BUSY) { + uv_stop(loop); + uv_walk(loop, &closeWalkCb, null); + _ = uv_run(loop, .default); + bun.debugAssert(uv_loop_close(loop) == .zero); + } } + + threadlocal_loop = null; } threadlocal var threadlocal_loop_data: Loop = undefined; @@ -2115,7 +2130,7 @@ pub const uv_free_func = ?*const fn (?*anyopaque) callconv(.c) void; pub extern fn uv_library_shutdown() void; pub extern fn uv_replace_allocator(malloc_func: uv_malloc_func, realloc_func: uv_realloc_func, calloc_func: uv_calloc_func, free_func: uv_free_func) c_int; pub extern fn uv_loop_init(loop: *uv_loop_t) ReturnCode; -pub extern fn uv_loop_close(loop: *uv_loop_t) c_int; +pub extern fn uv_loop_close(loop: *uv_loop_t) ReturnCode; pub extern fn uv_loop_new() *uv_loop_t; pub extern fn uv_loop_delete(*uv_loop_t) void; pub extern fn uv_loop_size() usize; From 437d26532271b48ad1f176312e98ee86f91a8c69 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Tue, 18 Nov 2025 14:05:55 -0800 Subject: [PATCH 3/6] fix build --- src/bun.js/web_worker.zig | 5 ++++- src/deps/libuv.zig | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/bun.js/web_worker.zig b/src/bun.js/web_worker.zig index d1e73f3301e7..76ade7ec99e2 100644 --- a/src/bun.js/web_worker.zig +++ b/src/bun.js/web_worker.zig @@ -622,7 +622,9 @@ pub fn exitAndDeinit(this: *WebWorker) noreturn { arena_.deinit(); } - bun.windows.libuv.Loop.close(); + if (comptime Environment.isWindows) { + bun.windows.libuv.Loop.shutdown(); + } bun.exitThread(); } @@ -641,6 +643,7 @@ const bun = @import("bun"); const Async = bun.Async; const Output = bun.Output; const assert = bun.assert; +const Environment = bun.Environment; const jsc = bun.jsc; const JSValue = jsc.JSValue; diff --git a/src/deps/libuv.zig b/src/deps/libuv.zig index c9d349beae73..b12c599e456f 100644 --- a/src/deps/libuv.zig +++ b/src/deps/libuv.zig @@ -662,7 +662,7 @@ pub const Loop = extern struct { } } - pub fn close() void { + pub fn shutdown() void { const loop = threadlocal_loop orelse { return; }; From b38682ef179ca8114302ba9bc4a64698309782b6 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 18 Nov 2025 22:07:38 +0000 Subject: [PATCH 4/6] [autofix.ci] apply automated fixes --- src/bun.js/web_worker.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bun.js/web_worker.zig b/src/bun.js/web_worker.zig index 76ade7ec99e2..867d49c96baa 100644 --- a/src/bun.js/web_worker.zig +++ b/src/bun.js/web_worker.zig @@ -641,9 +641,9 @@ const WTFStringImpl = @import("../string.zig").WTFStringImpl; const bun = @import("bun"); const Async = bun.Async; +const Environment = bun.Environment; const Output = bun.Output; const assert = bun.assert; -const Environment = bun.Environment; const jsc = bun.jsc; const JSValue = jsc.JSValue; From 771fdf50ad92f3c7ad9a5f37469eb54ea350ca71 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Tue, 18 Nov 2025 15:58:13 -0800 Subject: [PATCH 5/6] update --- src/bun.js/web_worker.zig | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/bun.js/web_worker.zig b/src/bun.js/web_worker.zig index 867d49c96baa..b09be6a093ef 100644 --- a/src/bun.js/web_worker.zig +++ b/src/bun.js/web_worker.zig @@ -611,6 +611,10 @@ pub fn exitAndDeinit(this: *WebWorker) noreturn { loop_.internal_loop_data.jsc_vm = null; } + if (comptime Environment.isWindows) { + bun.windows.libuv.Loop.shutdown(); + } + this.deinit(); if (vm_to_deinit) |vm| { @@ -622,10 +626,6 @@ pub fn exitAndDeinit(this: *WebWorker) noreturn { arena_.deinit(); } - if (comptime Environment.isWindows) { - bun.windows.libuv.Loop.shutdown(); - } - bun.exitThread(); } From 7b32474433123b8bf82178e995cd610909bdfb12 Mon Sep 17 00:00:00 2001 From: Zack Radisic Date: Wed, 19 Nov 2025 17:10:27 -0800 Subject: [PATCH 6/6] fixup --- src/bun.js/web_worker.zig | 7 ++++++- src/deps/libuv.zig | 1 - 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/bun.js/web_worker.zig b/src/bun.js/web_worker.zig index b09be6a093ef..d74686cfaa21 100644 --- a/src/bun.js/web_worker.zig +++ b/src/bun.js/web_worker.zig @@ -611,6 +611,12 @@ pub fn exitAndDeinit(this: *WebWorker) noreturn { loop_.internal_loop_data.jsc_vm = null; } + if (vm_to_deinit) |vm| { + // this deinit needs to happen before `Loop.shutdown` + // in order to not call uv_close on the gc timer twice. + vm.gc_controller.deinit(); + } + if (comptime Environment.isWindows) { bun.windows.libuv.Loop.shutdown(); } @@ -618,7 +624,6 @@ pub fn exitAndDeinit(this: *WebWorker) noreturn { this.deinit(); if (vm_to_deinit) |vm| { - vm.gc_controller.deinit(); vm.deinit(); // NOTE: deinit here isn't implemented, so freeing workers will leak the vm. } bun.deleteAllPoolsForThreadExit(); diff --git a/src/deps/libuv.zig b/src/deps/libuv.zig index b12c599e456f..7e8db2d0abab 100644 --- a/src/deps/libuv.zig +++ b/src/deps/libuv.zig @@ -669,7 +669,6 @@ pub const Loop = extern struct { if (uv_loop_close(loop).errEnum()) |err| { if (err == .BUSY) { - uv_stop(loop); uv_walk(loop, &closeWalkCb, null); _ = uv_run(loop, .default); bun.debugAssert(uv_loop_close(loop) == .zero);