Thanks to visit codestin.com
Credit goes to github.com

Skip to content

daemon/logger: fix goroutine leak in ringLogger when Log() blocks#52043

Open
4RH1T3CT0R7 wants to merge 1 commit intomoby:masterfrom
4RH1T3CT0R7:fix/ring-logger-goroutine-leak-51301
Open

daemon/logger: fix goroutine leak in ringLogger when Log() blocks#52043
4RH1T3CT0R7 wants to merge 1 commit intomoby:masterfrom
4RH1T3CT0R7:fix/ring-logger-goroutine-leak-51301

Conversation

@4RH1T3CT0R7
Copy link

@4RH1T3CT0R7 4RH1T3CT0R7 commented Feb 14, 2026

Summary

  • Fix goroutine leak in ringLogger.run() when the underlying logger's Log() method blocks indefinitely (e.g. unresponsive fluentd/syslog backend), which also causes Close() to hang forever on wg.Wait().
  • Wrap Log() in a sub-goroutine within run(), using a closeCh channel to allow prompt exit when Close() is called.
  • Track the in-flight Log() goroutine via orphanedLog so that Close() waits for it (up to 5 seconds) before accessing the underlying logger, preventing concurrent Log()/Close() calls that can panic in some drivers (e.g. fluentd, as fixed in cf259eb).

Background

Issue #51301 reports that when reading a log message times out, the run() goroutine leaks because it is stuck in a blocking r.l.Log(msg) call. Closing the ring buffer unblocks Dequeue() but cannot unblock Log(), so Close() never returns.

Root cause

In ringLogger.run(), the call to r.l.Log(msg) is synchronous. If the underlying logger blocks (network logger with unresponsive backend, full disk, etc.), the goroutine is permanently stuck. Close() calls r.buffer.Close() (which only unblocks Dequeue) and then r.wg.Wait(), which blocks forever.

Fix

  1. closeCh chan struct{}: A new channel closed by Close() to signal run() to stop.
  2. Sub-goroutine for Log(): In run(), each r.l.Log(msg) call is wrapped in a goroutine with a select on logDone (Log completed) and closeCh (Close called). This allows run() to exit even if Log() is blocked.
  3. orphanedLog chan struct{}: When run() exits via closeCh, it saves the pending logDone channel. Close() waits on it (with a 5-second timeout) before proceeding to drain and close the underlying logger. This prevents the concurrent Log()/Close() race that previously caused panics in the fluentd driver.

Safety guarantees

  • Happens-before: orphanedLog is written by run() before wg.Done(), read by Close() after wg.Wait() - the WaitGroup provides the happens-before relationship.
  • Serialization: At most one Log() goroutine is in flight at a time - the select blocks until the current one completes or closeCh fires.
  • Backward compatibility: Normal operation (Log completes promptly) is unaffected - the <-logDone path is taken and run() loops as before.

Test plan

  • New test TestRingLoggerCloseWithBlockingLog verifies Close() does not hang when Log() is blocked
  • All existing ring logger tests pass (TestRingLogger, TestRingCap, TestRingClose, TestRingDrain)
  • go vet ./daemon/logger/ passes
  • go build ./daemon/logger/ succeeds

Fixes #51301

When the underlying logger's Log() method blocks (e.g. due to an
unresponsive fluentd/syslog backend), the run() goroutine gets stuck
and Close() hangs forever on wg.Wait(), leaking the goroutine.

Fix this by running Log() in a sub-goroutine within run(), with a
select on a new closeCh channel. When Close() is called, closeCh is
closed, allowing run() to exit promptly. The in-flight Log() goroutine
is tracked via orphanedLog so that Close() waits for it (up to 5s)
before accessing the underlying logger, preventing concurrent
Log()/Close() calls that can panic in some drivers.

Fixes moby#51301

Signed-off-by: 4RH1T3CT0R7 <[email protected]>
@4RH1T3CT0R7 4RH1T3CT0R7 force-pushed the fix/ring-logger-goroutine-leak-51301 branch from e7802c7 to 8440181 Compare February 14, 2026 15:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A potential blocking concurrency bug in daemon/logger/ring.go

1 participant