fix: null file slot on backend track failure to prevent double-close - #316
Open
detail-app[bot] wants to merge 1 commit into
Open
detail-app[bot] wants to merge 1 commit into
detail-app[bot] wants to merge 1 commit into
Conversation
ApprovabilityVerdict: Approved at Macroscope's review found this PR approvable — This is a focused cleanup fix in one watcher module that restores the file-slot invariant on backend registration failures and prevents duplicate memory ownership cleanup. Existing successful behavior remains unchanged, and the added regression test is isolated to testing. You can add or adjust custom eligibility rules. Learn more. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Detail bug report: View on Detail
Fixes ENG-717
Bug
Watcher.openTracked(src/tail/watch.zig) committed a freshly-opened file handle intoself.files.items[idx]before the finaltry self.backendTrackOpenFile(...), but itserrdefer file.close(self.io)only closed the file on error — it left the slot pointing at the now-closed handle. WhenbackendTrackOpenFileerrored after the slot commit (e.g. macOS kqueueerror.AccessDeniedfromensureDirectoryWatchon a search-but-not-read directory, or Linux io_uringerror.OutOfMemoryfrom the directory-watch hashmap puts),Watcher.init'serrdefer self.deinit()— and the production runtime'sdefer watcher.deinit()— re-close()d the already-closed fd.Debugthis hits kernelEBADF→std.Io.Threaded.closeFd'srecoverableOsBugDetected()→unreachablepanic.ReleaseSafe/ReleaseFasttheEBADFis swallowed, but thedeinitinvariant ("every non-nullfiles[i]is an open handle") is still violated.Introduced in a32652c, which appended the
tryafter the slot commit without updating the errdefer.Fix
Two local changes so a backend-registration failure propagates cleanly through
Watcher.init/deinit:openTracked's errdefer now nulls the slot in the same block that closes the file, restoring the invariantdeinitrelies on:Watcher.inithad a latent double-free on the same path: itserrdefer allocator.free(out_copy)(and theinput_copyerrdefer) fire aftererrdefer self.deinit()(LIFO) and re-free memorydeinitalready released. Removed the redundantout_copyerrdefer (the dupe is moved intoself.output_pathby a non-errorable struct literal, so the errdefer covers no errorable window) and detachedinput_copy = .emptyonceselftakes ownership. Without this, the report's primary macOS repro (which fails duringWatcher.init) would merely move the crash from the file double-close to theout_copydouble-free.Testing
src/tail/watch.zigdrives the post-slot-commit failure window via the Linux io_uring backend (OOM inbackendTrackOpenFile) with an allocation-failure sweep and assertsWatcher.initeither succeeds or returns a clean error, never panics. Verified by negative control: reverting the slot fix makes the test reproduce the exactEBADF → unreachablepanic from the bug (the documented call chain isopenTracked → backendTrackOpenFile → init errdefer self.deinit() → deinit f.close() → Threaded.closeFd EBADF → recoverableOsBugDetected); restoring the fix turns it green.zig build;zig build test(Debug, where the panic is observable) andzig build test -Doptimize=ReleaseSafe(the CI mode);zig fmt --check;ziglint;zig build -Doptimize=ReleaseSafe; thetaildistribution build; both HTTP frontends (httpz/stdio); andtask do(the project's pre-merge flow).bench/logging, pytest): glob discovery, checkpoint recovery/resume, append, concurrency stress, and the non-DAC error-handling tests pass, with no regressions in rotation lifecycle. Two pre-existing, unrelated failures were confirmed against the unmodified source — a literal-missing-path re-discovery case, and twochmod 0tests that hang when run as root (root bypasses DAC).--x-directory repro could not be exercised on this Linux host (the kqueue backend iscomptime-gated to.macos). It is covered by code equivalence: the io_uring OOM driver exercises the sameopenTrackederrdefer/deinitpath under test, andopenTrackedis the sole site that commits a freshly-opened handle into a slot.Automatic Fixes PRs can be configured here.