feat: add graceful shutdown with OS signal handling

Handle SIGINT/SIGTERM in Start() to cleanly drain all contexts,
stop goroutines, close SSE connections, and tear down PubSub.

Fix stopAllRoutines() to close() the channel instead of sending a
single value, so all listening goroutines are notified.
This commit is contained in:
Ryan Hamamura
2026-01-31 09:22:43 -10:00
parent a7ace9099f
commit 4a7acbb630
2 changed files with 96 additions and 11 deletions

View File

@@ -32,6 +32,7 @@ type Context struct {
reqCtx context.Context
subscriptions []Subscription
subsMu sync.Mutex
disposeOnce sync.Once
}
// View defines the UI rendered by this context.
@@ -350,11 +351,23 @@ func (c *Context) ReplaceURLf(format string, a ...any) {
c.ReplaceURL(fmt.Sprintf(format, a...))
}
// stopAllRoutines stops all go routines tied to this Context preventing goroutine leaks.
// dispose idempotently tears down this context: unsubscribes all pubsub
// subscriptions and closes ctxDisposedChan to stop routines and exit the SSE loop.
func (c *Context) dispose() {
c.disposeOnce.Do(func() {
c.unsubscribeAll()
c.stopAllRoutines()
})
}
// stopAllRoutines closes ctxDisposedChan, broadcasting to all listening
// goroutines (OnIntervalRoutine, SSE loop) that this context is done.
func (c *Context) stopAllRoutines() {
select {
case c.ctxDisposedChan <- struct{}{}:
case <-c.ctxDisposedChan:
// already closed
default:
close(c.ctxDisposedChan)
}
}