Make /api/shutdown actually exit the server process - #107
Merged
Conversation
handleShutdown only closed the HTTP listeners, leaving the Run loop blocked on <-ctx.Done() waiting for a signal. stopBackgroundService saw the port go unresponsive, assumed the stop succeeded, and started a fresh server — while the old process kept running. Each restart therefore left a leftover csghub-lite serve process behind. Store the signal context's cancel on the Server and invoke it from handleShutdown so the process exits via the same path as SIGINT/SIGTERM.
ganisback
approved these changes
Aug 10, 2026
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
restart(andstop-service) left a leftovercsghub-lite serveprocess behind each time, so processes accumulated.Root cause:
handleShutdownonly calleds.http.Shutdown(), which closes the HTTP listeners. But theRunloop is blocked on<-ctx.Done()waiting for a signal that never comes via the HTTP path, so the process never exits.stopBackgroundServicepolls the health endpoint, sees it go unresponsive, assumes the stop succeeded, and starts a fresh server — while the old process keeps running.Fix
Store the
signal.NotifyContextcancel function on theServerand invoke it fromhandleShutdownafter closing the listeners. This drivesRunthrough the same exit path asSIGINT/SIGTERM(graceful engine + listener shutdown), so the process actually terminates.http.Server.Shutdownis idempotent (called once here, once in the Run exit path) — safe.shutdownRuntime(closeAllEngines+CloseAll) is idempotent — safe to run twice.shutdownCancelis nil-guarded; only set inRun, which always runs before the route can be hit.Note
This prevents new orphans from accumulating. Existing leftover processes from before this fix are not auto-cleaned; clear them once with
pkill -f "csghub-lite serve".Before:

After:
