fix: handle SIGINT/SIGTERM during initial generation - #772
Open
JamBalaya56562 wants to merge 1 commit into
Open
Conversation
Generate() ran the initial synchronous generateFromContainers() before any of the watch loops, and signal.Notify was only ever called from inside those loops. A SIGINT or SIGTERM arriving during that window therefore hit the Go default disposition and killed the process outright. cmd/docker-gen/main.go already guards the same class of problem for SIGHUP via signal.Ignore, but SIGINT and SIGTERM were left unprotected. Register the signal channel before the initial generation and check it once generation completes, so the process shuts down gracefully instead of dying. signal.Ignore is deliberately not used for these two: ignoring them would make docker stop hang until its timeout and then SIGKILL. The channel is only registered when a config actually watches, matching the existing decision in generateFromSignals not to watch signals otherwise. The signal channel constructor is now injectable so the ordering can be tested without sending real signals, which also keeps the test working on Windows. Fixes nginx-proxy#201 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Problem
Generate()runs the initial, synchronousgenerateFromContainers()before any ofthe watch loops start:
signal.Notifyis what suppresses the Go default disposition, and it was only evercalled from inside those goroutines. A
SIGINTorSIGTERMarriving during theinitial generation therefore killed the process outright. The window is not
negligible — the initial pass makes one Docker API round trip per container.
cmd/docker-gen/main.goalready guards this exact class of problem, but only forSIGHUP:SIGINTandSIGTERMwere left unprotected, which matches the exit status reportedin #201.
Change
generation completes, so docker-gen finishes the in-flight generation and then
exits gracefully.
signal.Ignoreis deliberately not used forSIGINT/SIGTERM. Ignoring themwould make
docker stopduring startup hang until its timeout and thenSIGKILL—worse than the current behaviour. They have to be received and handled.
existing decision in
generateFromSignalsnot to watch signals when there isnothing to watch. One-shot runs keep their current abort semantics.
hasWatcher()is extracted fromgenerateFromSignalsso both call sites share it.Implementation notes
The signal channel constructor is now injectable on the generator, following the same
pattern as
GetCurrentContainerID. This makes the ordering testable without sendingreal signals to the test process, which also keeps the test running on Windows rather
than being skipped.
Tests
TestGenerateStopsOnSignalDuringInitialGenerationcoversSIGINTandSIGTERM. Theinjected constructor records that it ran, and the
/containers/jsonhandler assertsthat flag was already set when the initial generation reached it — so the test fails
if registration ever moves back after generation. It also asserts
/eventsis neverrequested, confirming the watch loops do not start.
Scope
The other causes discussed in #201 are already resolved: the "is a directory" reports
were a bind-mount user error, and the SIGHUP race was fixed by the
signal.Ignorecall above plus the graceful handler. This PR closes the remaining gap.
Fixes #201
🤖 Generated with Claude Code