-
Notifications
You must be signed in to change notification settings - Fork 3.7k
fix(windows): close handles immediately on shutdown notification to p… #24989
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,6 +7,8 @@ iocp: w.HANDLE = undefined, | |
| watcher: DirWatcher = undefined, | ||
| buf: bun.PathBuffer = undefined, | ||
| base_idx: usize = 0, | ||
| /// Track if handles have been closed to prevent double-close | ||
| handles_closed: bool = false, | ||
|
|
||
| pub const EventListIndex = c_int; | ||
|
|
||
|
|
@@ -173,9 +175,9 @@ pub fn next(this: *WindowsWatcher, timeout: Timeout) bun.sys.Maybe(?EventIterato | |
| continue; | ||
| } | ||
| if (nbytes == 0) { | ||
| // shutdown notification | ||
| // TODO close handles? | ||
| // shutdown notification - close handles immediately to prevent resource leaks | ||
| log("shutdown notification in WindowsWatcher.next", .{}); | ||
| this.closeHandles(); | ||
| return .{ .err = .{ | ||
|
Comment on lines
177
to
181
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Good: close on shutdown; optionally short‑circuit Closing handles on the One small hardening you might consider: if pub fn next(this: *WindowsWatcher, timeout: Timeout) bun.sys.Maybe(?EventIterator) {
+ if (this.handles_closed) {
+ return .{ .err = .{
+ .errno = @intFromEnum(bun.sys.SystemErrno.ESHUTDOWN),
+ .syscall = .watch,
+ } };
+ }
+
switch (this.watcher.prepare()) {Not required for correctness given current callers, but it makes the API more self‑defensive.
🤖 Prompt for AI Agents |
||
| .errno = @intFromEnum(bun.sys.SystemErrno.ESHUTDOWN), | ||
| .syscall = .watch, | ||
|
|
@@ -192,9 +194,35 @@ pub fn next(this: *WindowsWatcher, timeout: Timeout) bun.sys.Maybe(?EventIterato | |
| } | ||
| } | ||
|
|
||
| /// Close handles if they haven't been closed already. | ||
| /// This function is idempotent and safe to call multiple times. | ||
| /// | ||
| /// When a shutdown notification is received from the IOCP, we need to close | ||
| /// handles immediately to prevent resource leaks. This function ensures handles | ||
| /// are properly closed and marked as invalid to prevent double-close errors. | ||
| fn closeHandles(this: *WindowsWatcher) void { | ||
| if (this.handles_closed) { | ||
| return; | ||
| } | ||
| this.handles_closed = true; | ||
|
|
||
| // Close directory handle if it's still valid | ||
| if (this.watcher.dirHandle != w.INVALID_HANDLE_VALUE) { | ||
| _ = bun.windows.CloseHandle(this.watcher.dirHandle); | ||
| this.watcher.dirHandle = w.INVALID_HANDLE_VALUE; | ||
| log("closed directory handle", .{}); | ||
| } | ||
|
|
||
| // Close IOCP handle if it's still valid | ||
| if (this.iocp != w.INVALID_HANDLE_VALUE) { | ||
| _ = bun.windows.CloseHandle(this.iocp); | ||
| this.iocp = w.INVALID_HANDLE_VALUE; | ||
| log("closed IOCP handle", .{}); | ||
| } | ||
| } | ||
|
|
||
| pub fn stop(this: *WindowsWatcher) void { | ||
| w.CloseHandle(this.watcher.dirHandle); | ||
| w.CloseHandle(this.iocp); | ||
| this.closeHandles(); | ||
| } | ||
|
|
||
| pub fn watchLoopCycle(this: *bun.Watcher) bun.sys.Maybe(void) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Reset
handles_closedininitto avoid leaks on reuseThe flag is great for preventing double-closes, but if a
WindowsWatcherinstance is ever re‑initialized (callinginitagain afterstop/shutdown),handles_closedwill still betrue, socloseHandles()becomes a no‑op and new handles will leak.Consider explicitly clearing it at the start of
init:pub fn init(this: *WindowsWatcher, root: []const u8) !void { + this.handles_closed = false; + var pathbuf: bun.WPathBuffer = undefined; const wpath = bun.strings.toNTPath(&pathbuf, root);This is a cheap change and makes the lifecycle more robust even if reuse is rare.
🤖 Prompt for AI Agents