Skip to content

daemon: close EventsService on shutdown#51448

Merged
thaJeztah merged 1 commit intomoby:masterfrom
akerouanton:stop-events-service
Nov 10, 2025
Merged

daemon: close EventsService on shutdown#51448
thaJeztah merged 1 commit intomoby:masterfrom
akerouanton:stop-events-service

Conversation

@akerouanton
Copy link
Member

- What I did

On daemon shutdown, the HTTP server tries to gracefully shutdown for 5 seconds. If there's an open API connection to the '/events' endpoint, it fails to do so as nothing interrupts that connection, thus forcing the daemon to wait until that timeout is reached.

Add a Close method to the EventsService, and call it during daemon shutdown. It'll close any events channel, signaling to the '/events' handler to return and close the connection.

It now takes ~1s (or less) to shutdown the daemon when there's an active '/events' connection, instead of 5.

- How to verify it

Within the dev container:

$ DELVE_PORT= KEEPBUNDLE=y ./hack/make.sh binary install-binary run &
$ wait %1; date

Run docker system events, kill dockerd and observe how much time it takes to shutdown.

Before (5s delay):

INFO[2025-11-09T18:27:19.773736464Z] Processing signal 'terminated'
…
INFO[2025-11-09T18:27:19.778574714Z] Daemon shutdown complete
ERRO[2025-11-09T18:27:24.781560217Z] Error shutting down http server               error="context canceled"
Sun Nov  9 18:27:24 UTC 2025

After (~1s or less):

INFO[2025-11-09T18:28:45.673503171Z] Processing signal 'terminated'
…
INFO[2025-11-09T18:28:45.678989546Z] Daemon shutdown complete
Sun Nov  9 18:28:46 UTC 2025

- Human readable description for the release notes

- Fix a bug preventing the API server from shutting down quickly when there's an open connection to the `/events` endpoint.

On daemon shutdown, the HTTP server tries to gracefully shutdown for 5
seconds. If there's an open API connection to the '/events' endpoint, it
fails to do so as nothing interrupts that connection, thus forcing the
daemon to wait until that timeout is reached.

Add a Close method to the EventsService, and call it during daemon
shutdown. It'll close any events channel, signaling to the '/events'
handler to return and close the connection.

It now takes ~1s (or less) to shutdown the daemon when there's an active
'/events' connection, instead of 5.

Signed-off-by: Albin Kerouanton <albin.kerouanton@docker.com>
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR fixes a bug where the Docker daemon's HTTP server fails to shut down gracefully within its 5-second timeout when there's an active connection to the /events endpoint. The fix adds a Close() method to the EventsService that closes event channels, allowing the API handler to return immediately and close the connection. This reduces shutdown time from 5+ seconds to ~1 second or less.

Key changes:

  • Added Close() method to Events struct that closes the publisher
  • Modified /events handler to detect closed channels and return gracefully
  • Called EventsService.Close() during daemon shutdown
  • Added integration test to verify shutdown timing with active events connection

Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
daemon/events/events.go Adds Close() method to terminate all event subscriber channels
daemon/daemon.go Calls EventsService.Close() during shutdown sequence
daemon/server/router/system/system_routes.go Handles closed event channels by checking ok flag and returning gracefully
integration/daemon/daemon_linux_test.go Adds regression test verifying daemon shuts down in under 5 seconds with active events connection

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


apiClient := d.NewClientT(t)
// Open a connection to the '/events' endpoint.
apiClient.Events(ctx, client.EventsListOptions{})
Copy link

Copilot AI Nov 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The returned event channel from apiClient.Events() is not being consumed or closed, which could lead to resource leaks. Consider either consuming the events in a goroutine or explicitly closing the channel before stopping the daemon.

Suggested change
apiClient.Events(ctx, client.EventsListOptions{})
eventsChan, eventsErrChan := apiClient.Events(ctx, client.EventsListOptions{})
// Consume the events channel in a goroutine to avoid resource leaks.
go func() {
for range eventsChan {
// Discard events
}
}()
// Optionally, consume errors as well
go func() {
for range eventsErrChan {
// Discard errors
}
}()

Copilot uses AI. Check for mistakes.
d.Stop(t)

dt := time.Since(t0)
if dt.Seconds() > 5 {
Copy link

Copilot AI Nov 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Consider using a constant for the 5-second threshold to match the HTTP server's graceful shutdown timeout mentioned in the PR description, improving maintainability if this timeout changes.

Copilot uses AI. Check for mistakes.
@akerouanton akerouanton requested review from robmry and vvoland November 9, 2025 22:04
@djs55
Copy link
Contributor

djs55 commented Nov 10, 2025

Thanks! I ran this through some Docker Desktop CI tests: all green; and in my logs I see:

{"component":"dockerd","level":"info","msg":"Processing signal 'terminated'","time":"2025-11-10T11:11:20.839986667Z"}
{"component":"dockerd","level":"info","msg":"Daemon shutdown complete","time":"2025-11-10T11:11:20.842574501Z"}
{"component":"dockerd","level":"info","msg":"dockerd stopped gracefully after 9.774083ms","time":"2025-11-10T11:11:20.849556376Z"}

Much faster!

Copy link
Member

@thaJeztah thaJeztah left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@thaJeztah thaJeztah merged commit 048ced8 into moby:master Nov 10, 2025
193 checks passed
@thaJeztah thaJeztah added this to the 29.0.0 milestone Nov 10, 2025
@akerouanton akerouanton deleted the stop-events-service branch November 11, 2025 21:22
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.

5 participants