Назад
Golang
Context и graceful shutdown в Go
Как не терять запросы и не плодить утечки горутин
Как не стоит завершать приложение:
sig := make(chan os.Signal, 1)
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
<-sig
server.Close() // ← все запросы в полёте умирают
Последствия:
- клиенты получают 499 / EOF / таймаут
- горутины висят или паникуют
- в Kubernetes под застревает в TERM → SIGKILL через grace period
Современный подход (Go 1.16+)
func main() {
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
srv := &http.Server{
Addr: ":8080",
Handler: router,
BaseContext: func(net.Listener) context.Context {
return ctx // все новые запросы получают контекст, который отменится при SIGTERM
},
}
// Запускаем сервер в отдельной горутине
go func() {
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Printf("HTTP server error: %v", err)
}
}()
// Ждём сигнал
<-ctx.Done()
log.Println("Shutdown signal received")
// Даём время на завершение запросов (25–30 секунд — типичный таймаут)
shutdownCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := srv.Shutdown(shutdownCtx); err != nil {
log.Printf("Graceful shutdown error: %v", err)
// Опционально: srv.Close() для жёсткого завершения
}
log.Println("Server stopped gracefully")
}
Ключевые моменты, которые спасают от утечек
-
Передавайте
ctxво все длительные операции- БД: QueryContext, ExecContext
- Redis: Do(ctx, cmd)
- gRPC: ctx в каждом вызове
- Свои горутины: select { case <-ctx.Done(): return }
-
Readiness / Liveness в Kubernetes
Как только получили сигнал → /readyz → 503 → kube перестаёт слать трафик. -
Фоновые воркеры
Используйте sync.WaitGroup + общий ctx:
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for {
select {
case <-ctx.Done():
return
case job := <-queue:
process(job)
}
}
}()
}
// В shutdown: после srv.Shutdown() делаем wg.Wait()
Что стоит учитывать
- signal.NotifyContext → глобальный appCtx
- BaseContext в http.Server
- srv.Shutdown(with timeout)
- Все длительные операции обязательно используют context
- sync.WaitGroup / errgroup для фоновых воркеров
Будте здоровы и не забывайте про контекст и длительные операции🙃