Skip to content

Avoid deadlocks when closing follower - #15

Open
seanlinsley wants to merge 6 commits into
papertrail:masterfrom
pganalyze:deadlock-on-rotated-file
Open

Avoid deadlocks when closing follower#15
seanlinsley wants to merge 6 commits into
papertrail:masterfrom
pganalyze:deadlock-on-rotated-file

Conversation

@seanlinsley

Copy link
Copy Markdown

Close deadlocks when follow is blocked in the inner read loop (ReadBytes waiting for data). The closeCh select is only in the outer loop, so Close blocks forever on the unbuffered closeCh send.

This is addressed by making closeCh a buffered channel to prevent a deadlock, and avoiding potential dangling file descriptors by immediately closing the file instead of deferring to closeCh.

lfittl added 5 commits July 13, 2026 12:20
Reading from a closed channel always returns immediately, and when
close gets invoked waiting Go routines are instantly unblocked. This is
a more reliable mechanism than using a buffered channel for closeCh,
which could theoretically block on a third call to the Follower Close()
method. Use sync.Once to ensure the channel is only closed once, since
subsequent close calls would otherwise panic.
The file is assumed to be owned and only modified by the internally
running tail Go routine, and closing the file directly causes a data
race, as reported by the Go runtime. Whilst we could wrap the file
in a mutex to allow eager closure, we can rely on the tail routine
itself to perform the close, especially after switching to using channel
closure instead of a buffered channel.
This ensures that we react correctly to the follower being closed,
instead of potentially blocking on writing to the lines channel. The logic
here matches what we do when we notice the close channel being closed
later in the same routine.
Previously we re-opened the file exactly once after a rename or remove
event, assuming an atomic log rotation operation. However, as is likely
in practice, if the rotation program had not yet created the new file, the
open failed and the follower exited, permanently stopping the tail.

To fix, retry every 50ms for up to 1 second, but only when we actually
get a failure due to the file not existing, as was already intended to be
implemented and documented in a code comment.
This test method was previously only checking errors that occur immediately
when the log tail is set up, and was doing so whilst also actively
reading from Lines in a separate Go routine. That is not safe to do due
to the lack of a supporting synchronization primitive.

Instead, first read all lines, and then check for errors. This
depends on the recent TestRenameCreate fix which ensures we don't error
out mid-test in some cases (which was previously silently ignored).

As reported when running the tests with the "-race" argument.
@seanlinsley

seanlinsley commented Jul 14, 2026

Copy link
Copy Markdown
Author

Note: pganalyze#1 extended this PR with additional changes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants