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
129 lines
3.3 KiB
Go
129 lines
3.3 KiB
Go
package routewatch
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"os/signal"
|
|
"runtime"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
|
|
"git.eeqj.de/sneak/routewatch/internal/logger"
|
|
"go.uber.org/fx"
|
|
)
|
|
|
|
const (
|
|
// shutdownTimeout is how long Shutdown has after a stop signal. upaas,
|
|
// like docker stop by default, kills the container 10 seconds after the
|
|
// stop signal (README "Running under upaas").
|
|
shutdownTimeout = 8 * time.Second
|
|
// stopTimeout is fx's limit on the stop hook, past which fx exits with
|
|
// status 1: shutdownTimeout and time for Shutdown to close the HTTP
|
|
// server after its deadline.
|
|
stopTimeout = 9 * time.Second
|
|
// debugInterval is how often to log debug stats
|
|
debugInterval = 60 * time.Second
|
|
// bytesPerMB is bytes per megabyte
|
|
bytesPerMB = 1024 * 1024
|
|
// failureExitCode is the exit status when RouteWatch stops on an error
|
|
failureExitCode = 1
|
|
)
|
|
|
|
// logDebugStats logs goroutine count and memory usage
|
|
func logDebugStats(logger *logger.Logger) {
|
|
// Only run if DEBUG env var contains "routewatch"
|
|
debugEnv := os.Getenv("DEBUG")
|
|
if !strings.Contains(debugEnv, "routewatch") {
|
|
return
|
|
}
|
|
|
|
ticker := time.NewTicker(debugInterval)
|
|
defer ticker.Stop()
|
|
|
|
for range ticker.C {
|
|
var m runtime.MemStats
|
|
runtime.ReadMemStats(&m)
|
|
|
|
logger.Debug("System stats",
|
|
"goroutines", runtime.NumGoroutine(),
|
|
"alloc_mb", m.Alloc/bytesPerMB,
|
|
"total_alloc_mb", m.TotalAlloc/bytesPerMB,
|
|
"sys_mb", m.Sys/bytesPerMB,
|
|
"num_gc", m.NumGC,
|
|
"heap_alloc_mb", m.HeapAlloc/bytesPerMB,
|
|
"heap_sys_mb", m.HeapSys/bytesPerMB,
|
|
"heap_idle_mb", m.HeapIdle/bytesPerMB,
|
|
"heap_inuse_mb", m.HeapInuse/bytesPerMB,
|
|
"heap_released_mb", m.HeapReleased/bytesPerMB,
|
|
"stack_inuse_mb", m.StackInuse/bytesPerMB,
|
|
)
|
|
}
|
|
}
|
|
|
|
// CLIEntry is the main entry point for the routewatch command-line interface.
|
|
// It initializes the application using the fx dependency injection framework,
|
|
// sets up signal handling for graceful shutdown, and starts the RouteWatch
|
|
// service. This function blocks until the application receives a shutdown
|
|
// signal or encounters a fatal error.
|
|
func CLIEntry() {
|
|
app := fx.New(
|
|
fx.Options(getModule()...),
|
|
fx.StopTimeout(stopTimeout),
|
|
fx.Invoke(func(
|
|
lc fx.Lifecycle,
|
|
rw *RouteWatch,
|
|
logger *logger.Logger,
|
|
shutdowner fx.Shutdowner,
|
|
) {
|
|
lc.Append(fx.Hook{
|
|
OnStart: func(ctx context.Context) error {
|
|
// Start debug stats logging
|
|
go logDebugStats(logger)
|
|
|
|
// Handle shutdown signals
|
|
sigCh := make(chan os.Signal, 1)
|
|
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
|
|
|
|
go func() {
|
|
<-sigCh
|
|
logger.Info("Received shutdown signal")
|
|
|
|
err := shutdowner.Shutdown()
|
|
if err != nil {
|
|
logger.Error("Failed to shutdown gracefully", "error", err)
|
|
}
|
|
}()
|
|
|
|
go func() {
|
|
err := rw.Run(ctx)
|
|
if err != nil {
|
|
logger.Error("RouteWatch error", "error", err)
|
|
|
|
err = shutdowner.Shutdown(fx.ExitCode(failureExitCode))
|
|
if err != nil {
|
|
logger.Error("Failed to shutdown gracefully",
|
|
"error", err)
|
|
}
|
|
}
|
|
}()
|
|
|
|
return nil
|
|
},
|
|
OnStop: func(ctx context.Context) error {
|
|
logger.Info("Shutting down RouteWatch")
|
|
|
|
ctx, cancel := context.WithTimeout(ctx, shutdownTimeout)
|
|
defer cancel()
|
|
|
|
rw.Shutdown(ctx)
|
|
|
|
return nil
|
|
},
|
|
})
|
|
}),
|
|
)
|
|
|
|
app.Run()
|
|
}
|