fix(tail): free dir-path alloc when a directory watch fails - #310
detail-app[bot] wants to merge 2 commits into
Conversation
| _ = @import("read_scheduler/common.zig"); | ||
| _ = @import("read_scheduler/poll.zig"); | ||
| _ = @import("watch_backend/poll.zig"); | ||
| _ = @import("watch_backend/uring_linux.zig"); |
There was a problem hiding this comment.
🟡 Medium tail/mod.zig:35
Registering watch_backend/uring_linux.zig makes its regression test fail the entire Linux suite when IoUring.init returns error.PermissionDenied or error.SystemOutdated under seccomp or older kernels. The test must skip these unavailable-environment errors instead of propagating them.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @src/tail/mod.zig around line 35:
Registering `watch_backend/uring_linux.zig` makes its regression test fail the entire Linux suite when `IoUring.init` returns `error.PermissionDenied` or `error.SystemOutdated` under seccomp or older kernels. The test must skip these unavailable-environment errors instead of propagating them.
ApprovabilityVerdict: Approved at Macroscope's review found this PR approvable — This is a small, localized tail-watcher bug fix that frees duplicated directory paths on failed watches and corrects Linux errno handling, with platform-specific regression tests. Existing successful watch behavior remains unchanged, and unavailable io_uring environments are skipped by the test. You can add or adjust custom eligibility rules. Learn more. |
…enied/SystemOutdated
Detail bug report: View on Detail
Fixes ENG-711
Bug
In the tail watcher,
ensureDirectoryWatch(in bothkqueue_macos.ziganduring_linux.zig) allocates adupe'd copy of the directory path and guards it witherrdefer … free. On failure it then swallows the error with a barereturn;(a normal void return from a!voidfunction). In Zig,errdeferonly runs on an error return, soerrdefernever fires on these paths and the path buffer is leaked.The kqueue impact is severe:
collectDirtycallsensureDirectoryWatches(which iterates every tracked path) on every poll cycle (defaultpoll_ms = 200, i.e. 5×/s). A trailing literal path whose parent directory doesn't exist re-enters the slow path every cycle and leaksowned_dir_patheach time, unbounded, for the lifetime of the process. The common trigger is startingedge tailbefore the application has created its log directory.Fix
kqueue_macos.zig: explicitlyfree(owned_dir_path)before the two normal-return failure paths —openDirreturningerror.FileNotFound, andkevent(...) < 0— preserving the existing "swallow as no-op" behavior.uring_linux.zig: same one-line fix on theinotifyAddWatch(...) catchpath.uring_linux.zig(supporting change): theinotifyInit1/inotifyAddWatchwrappers calledstd.posix.errno(rc)on a rawstd.os.linuxsyscall return. Because the build links libc,std.posix.errnoresolves tostd.c.errno, which only treats a return of-1as an error — but raw Linux syscalls return-errno(a largeusize), so every realinotify_add_watch/inotify_init1failure was misclassified asSUCCESSand then panicked at.SUCCESS => @intCast(rc). This meant the uring error path crashed the process instead of leaking or being handled. Switching tostd.os.linux.errno(type-identical tostd.c.Eon Linux) makes the wrappers return their typed errors so the leak fix'scatch { free; return; }actually executes.Testing
src/tail/mod.zigsozig build testdiscovers them): they callensureDirectoryWatch50× against a literal path whose parent directory doesn't exist under a tmp dir, relying onstd.testing.allocator's per-test leak detection. Pre-fix these would surface a leak; post-fix they pass.edge-tail(Debug) with the uring changes reverted, ran it with--io-engine uringover a missing-dir literal, deleted the glob-matched file to force eviction →rebuildIndexes→ensureDirectoryWatchon the missing dir, and captured the panicinteger does not fit in destination typeatinotifyAddWatch's.SUCCESS => @intCast(rc)with a full stack trace throughcollect → refreshPaths → evictExpiredUnmatched → removeTracked → rebuildIndexes → ensureDirectoryWatch → inotifyAddWatch(SIGABRT). Post-fix, the same scenario survives.pollanduringbackends both tail a growing file and emit appended lines to stdout (no regression); the uring backend survives an eviction-triggered rebuild over a missing-dir literal post-fix.zig build test(Debug and ReleaseSafe) passes (521/522, 1 pre-existing skip, 0 leaks),zig buildandzig build tailsucceed,zig fmt --checkandziglintare clean.aarch64-macos(zig build-obj/zig test -fno-emit-binexit 0), but executing the macOS binary yieldsExec format error(no emulator present). The kqueue leak fix follows the identical pattern as the uring fix and is unit-guarded by the same kind of regression test; it needs a macOS arm64 host for runtime verification.Automatic Fixes PRs can be configured here.