TGViewer
Библиотека Go-разработчика | Golang Библиотека Go-разработчика | Golang @goproglib · 24.1K subscribers
Post #7289 2.95K
✅ Graceful Shutdown в Go: полная защита с отклонением новых запросов

server.Shutdown() ждёт текущие запросы, но не закрывает входящий трафик мгновенно. Между сигналом и фактической остановкой сервер продолжает принимать новые соединения. При высокой нагрузке это окно создаёт проблемы.

Финальный паттерн закрывает и эту щель.

📎 Добавляем middleware для отклонения новых запросов

Используем атомарный флаг и промежуточный обработчик:
package main

import (
"context"
"fmt"
"net/http"
"os"
"os/signal"
"sync/atomic"
"syscall"
"time"
)

var isShuttingDown int32

func rejectMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if atomic.LoadInt32(&isShuttingDown) == 1 {
http.Error(w, "server is shutting down", http.StatusServiceUnavailable)
return
}
next.ServeHTTP(w, r)
})
}

func handler(w http.ResponseWriter, r *http.Request) {
id := r.URL.Query().Get("id")
fmt.Printf("START %s\n", id)
time.Sleep(5 * time.Second)
fmt.Printf("DONE %s\n", id)
w.Write([]byte("COMPLETED!"))
}

func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", handler)

server := &http.Server{
Addr: ":8080",
Handler: rejectMiddleware(mux),
}

go func() {
fmt.Println("server started at :8080")
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
panic(err)
}
}()

ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()

<-ctx.Done()
fmt.Println("shutdown signal received")

atomic.StoreInt32(&isShuttingDown, 1)

shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

server.Shutdown(shutdownCtx)

fmt.Println("graceful shutdown complete")
}


❔ Что здесь происходит

После получения сигнала сначала ставится флаг isShuttingDown = 1. rejectMiddleware проверяет этот флаг перед каждым запросом. Если флаг поднят, новый клиент получает 503 Service Unavailable. Уже выполняющиеся обработчики продолжают работу и завершаются штатно.

atomic.LoadInt32 и atomic.StoreInt32 нужны потому, что флаг читается и пишется из разных горутин. Без атомарных операций здесь была бы гонка данных.

Итоговая картина

Путь от первого паттерна до последнего выглядит так. Сначала сервер просто падает. Потом учится слышать сигналы. Потом начинает ждать текущие запросы. Наконец, закрывает вход для новых до того, как начнёт завершаться.

Каждый уровень добавляет одну гарантию. Финальный вариант даёт полный контроль над состоянием сервера во время остановки.

📍 Навигация: Вакансии • Задачи • Собесы

🐸 Библиотека Go-разработчика

#GoToProduction
  • ❤ 11
  • 🤔 1
More from @goproglib
  1. Sep 28, 2026🤔 Вопрос с собеседования по Go Что выведет программа? ❤️ — 1 true / 0 false 🔥 — 1 true /…
  2. Sep 28, 2026👩‍💻 Что на самом деле происходит внутри Go map? После Go 1.24 обычный map внутри работае…
  3. Sep 26, 2026🔥 В Go 1.27 появился portable SIMD До этого SIMD-оптимизации в Go требовали архитектурног…
  4. Sep 25, 2026🤡🤡 📍 Навигация: Вакансии • Задачи • Собесы 🐸 Библиотека Go-разработчика #GoGiggle
  5. Sep 25, 2026💡 Код работает. А data race уже есть В Go можно записать значение в одной горутине, прочи…
  6. Sep 23, 2026💥 TCP/IP: что происходит с данными в сети Когда Go-приложение отправляет данные по сети,…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →