Skip to content

fix: wait for background goroutines during shutdown#1756

Open
AmanGIT07 wants to merge 5 commits into
mainfrom
fix/track-background-goroutines
Open

fix: wait for background goroutines during shutdown#1756
AmanGIT07 wants to merge 5 commits into
mainfrom
fix/track-background-goroutines

Conversation

@AmanGIT07

Copy link
Copy Markdown
Contributor

Based on #1747 — merge that first; this base retargets to main after.

Summary

StartServer now waits for the UI server and the audit log listener to
finish shutting down before running deferred cleanup, instead of only
waiting for the connect server.

Changes

  • The log listener, UI server, and connect server run under an
    errgroup; StartServer returns g.Wait(), so deferred cleanup
    (database and service Close() calls) runs only after all three
    finish.
  • All three receive the group context, so a connect server startup
    failure also winds down the UI server and listener.
  • A cancelled context is no longer logged as "log listener failed" on
    normal shutdown.

Technical Details

errgroup.WithContext cancels the group context when any member
returns an error. Only ServeConnect returns a non-nil error, so
g.Wait() returns exactly what StartServer returned before.

Test Plan

🤖 Generated with Claude Code

AmanGIT07 and others added 4 commits July 10, 2026 15:55
The UI server ignored the shutdown signal and had no way to stop: it
used the package-level http.ListenAndServe, which offers no Shutdown.
Build a dedicated http.Server on a local mux, watch the context, and
drain requests within the grace period. The local mux also keeps
routes registered by other packages off the UI port.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The dist folder is gitignored except for a placeholder, so in CI the
embedded assets are empty and ServeUI exits before listening. Skip the
shutdown test in that case, matching the condition ServeUI itself
checks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 14, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
frontier Ready Ready Preview, Comment Jul 14, 2026 8:14pm

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b615a69a-5e8d-45b9-9dca-799486a2b72b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coveralls

coveralls commented Jul 14, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 29364880379

Coverage decreased (-0.02%) to 44.843%

Details

  • Coverage decreased (-0.02%) from the base build.
  • Patch coverage: 17 uncovered changes across 1 file (0 of 17 lines covered, 0.0%).
  • 89 coverage regressions across 3 files.

Uncovered Changes

File Changed Covered %
cmd/serve.go 17 0 0.0%

Coverage Regressions

89 previously-covered lines in 3 files lost coverage.

File Lines Losing Coverage Coverage
core/invitation/service.go 53 48.55%
cmd/serve.go 32 0.0%
internal/store/postgres/invitation_repository.go 4 80.3%

Coverage Stats

Coverage Status
Relevant Lines: 37625
Covered Lines: 16872
Line Coverage: 44.84%
Coverage Strength: 12.51 hits per line

💛 - Coveralls

@rohilsurana rohilsurana left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Verified the whole chain: the listener returns ctx.Err() on cancel, #1747's ServeUI returns on every path, and ServeConnect's drain is bounded, so g.Wait() cannot hang for more than the grace period. One suggestion inline about signal handling during that window.

Comment thread cmd/serve.go
Comment on lines +336 to +338
// Wait for every server to finish draining before the deferred cleanup
// above closes the database and other dependencies under them.
return g.Wait()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Now that StartServer actually waits out the drain, a second Ctrl-C during that window does nothing. ctx comes from signal.NotifyContext (line 124), and its stop function only runs via the defer when StartServer returns — so while the drain runs (up to ~10s), further SIGINT/SIGTERM are swallowed by the registration. Before this PR the process exited almost immediately, so it never mattered.

Unregistering the handler once shutdown starts restores default signal handling, giving operators "first signal drains, second signal quits now":

Suggested change
// Wait for every server to finish draining before the deferred cleanup
// above closes the database and other dependencies under them.
return g.Wait()
// Once shutdown starts, unregister the signal handler so a second
// SIGINT/SIGTERM gets default handling and can kill a stuck drain
// instead of being swallowed.
go func() {
<-gctx.Done()
cancelFunc()
}()
// Wait for every server to finish draining before the deferred cleanup
// above closes the database and other dependencies under them.
return g.Wait()

cancelFunc doubles as the stop function, and by the time gctx is done the context is already cancelled (either by a signal or by a member error), so calling it here only unregisters the handler.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch — the swallow-window only became real once the drain actually waits. Applied as suggested in 9a90116: the handler unregisters when shutdown starts, so the first signal drains and a second one quits immediately.

A second SIGINT/SIGTERM during the drain was swallowed by the
NotifyContext registration, leaving no way to force-quit a stuck
shutdown. Unregister the handler when shutdown begins so the second
signal gets default handling.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Base automatically changed from fix/ui-server-graceful-shutdown to main July 14, 2026 20:33
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.

3 participants