Bound each shutdown stage by the stop deadline (closes #100)
check / check (push) Canceled after 0s
check / check (push) Canceled after 0s
Shutdown passes its context to every stage: the streamer, the four handlers, the WHOIS fetcher, the database maintainer and the HTTP server each stop waiting when it ends. A handler then skips its last write and logs how many updates it did not write, and the HTTP server closes the connections still open. The database is checkpointed and closed last, and left open once the deadline has passed, since a write may still hold it. The stop hook gives Shutdown 8 seconds and fx 9, inside the 10 seconds upaas and docker stop allow, so a stop signal exits 0. Model: opus-5-5
This commit was merged in pull request #130.
This commit is contained in:
+41
-18
@@ -137,7 +137,12 @@ func (rw *RouteWatch) Run(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Shutdown performs graceful shutdown of all services
|
||||
// Shutdown stops all services in order: the streamer, the handlers, which
|
||||
// write what they hold, the WHOIS fetcher, the database maintainer and the
|
||||
// HTTP server, and last the database, which it checkpoints and closes. Each
|
||||
// stage waits at most until ctx ends, so Shutdown returns soon after: a
|
||||
// handler logs what it could not write by then, and the HTTP server closes
|
||||
// the connections of requests still running.
|
||||
func (rw *RouteWatch) Shutdown(ctx context.Context) {
|
||||
rw.mu.Lock()
|
||||
if rw.shutdown {
|
||||
@@ -150,51 +155,48 @@ func (rw *RouteWatch) Shutdown(ctx context.Context) {
|
||||
rw.mu.Unlock()
|
||||
|
||||
// Stop the streamer first: it returns once the handlers have been handed
|
||||
// every message it queued. Then each handler writes what it holds.
|
||||
rw.streamer.Stop()
|
||||
// every message it queued, or when ctx ends. Then each handler writes
|
||||
// what it holds.
|
||||
rw.streamer.Stop(ctx)
|
||||
|
||||
if rw.dbHandler != nil {
|
||||
rw.logger.Info("Flushing database handler")
|
||||
rw.dbHandler.Stop()
|
||||
rw.dbHandler.Stop(ctx)
|
||||
}
|
||||
|
||||
if rw.peerHandler != nil {
|
||||
rw.logger.Info("Flushing peer handler")
|
||||
rw.peerHandler.Stop()
|
||||
rw.peerHandler.Stop(ctx)
|
||||
}
|
||||
|
||||
if rw.prefixHandler != nil {
|
||||
rw.logger.Info("Flushing prefix handler")
|
||||
rw.prefixHandler.Stop()
|
||||
rw.prefixHandler.Stop(ctx)
|
||||
}
|
||||
|
||||
if rw.peeringHandler != nil {
|
||||
rw.logger.Info("Flushing peering handler")
|
||||
rw.peeringHandler.Stop()
|
||||
rw.peeringHandler.Stop(ctx)
|
||||
}
|
||||
|
||||
// Stop ASN WHOIS fetcher
|
||||
if rw.asnFetcher != nil {
|
||||
rw.asnFetcher.Stop()
|
||||
rw.asnFetcher.Stop(ctx)
|
||||
}
|
||||
|
||||
// Stop database maintainer
|
||||
if rw.dbMaintainer != nil {
|
||||
rw.dbMaintainer.Stop()
|
||||
rw.dbMaintainer.Stop(ctx)
|
||||
}
|
||||
|
||||
// Stop HTTP server with a timeout of its own, however much of ctx is left
|
||||
const serverStopTimeout = 5 * time.Second
|
||||
|
||||
stopCtx, cancel := context.WithTimeout(
|
||||
context.WithoutCancel(ctx), serverStopTimeout)
|
||||
defer cancel()
|
||||
|
||||
err := rw.server.Stop(stopCtx)
|
||||
err := rw.server.Stop(ctx)
|
||||
if err != nil {
|
||||
rw.logger.Error("Failed to stop HTTP server gracefully", "error", err)
|
||||
rw.logger.Error("Stopped the HTTP server before every request finished",
|
||||
"error", err)
|
||||
}
|
||||
|
||||
rw.closeDatabase(ctx)
|
||||
|
||||
// Log final metrics
|
||||
metrics := rw.streamer.GetMetrics()
|
||||
rw.logger.Info("Final metrics",
|
||||
@@ -206,6 +208,27 @@ func (rw *RouteWatch) Shutdown(ctx context.Context) {
|
||||
)
|
||||
}
|
||||
|
||||
// closeDatabase checkpoints and closes the database, unless ctx has ended: a
|
||||
// write may then still be running, and both would wait for it. SQLite rolls
|
||||
// back a write cut off by the exit when the database next opens.
|
||||
func (rw *RouteWatch) closeDatabase(ctx context.Context) {
|
||||
if ctx.Err() != nil {
|
||||
rw.logger.Error("Stop deadline passed; the database is not closed")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
err := rw.db.Checkpoint(ctx)
|
||||
if err != nil {
|
||||
rw.logger.Error("Failed to checkpoint the database", "error", err)
|
||||
}
|
||||
|
||||
err = rw.db.Close()
|
||||
if err != nil {
|
||||
rw.logger.Error("Failed to close the database", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
// getModule provides all fx dependencies
|
||||
func getModule() []fx.Option {
|
||||
return []fx.Option{
|
||||
|
||||
Reference in New Issue
Block a user