Когда дело доходит до завершения работы вашего веб-сервера, вы можете захотеть, чтобы ваш веб-сервер дождался обработки всех активных запросов перед выключением. Теперь мы обсудим, как добиться корректного завершения работы в Golang. Но прежде чем мы углубимся в код, важно понять, зачем нам нужно корректное завершение работы нашего веб-сервера.

Зачем нам это нужно?

Допустим, работник решает уйти с работы, обычно он уведомляет об этом своего работодателя. В течение периода уведомления они выполняют текущие задачи и передают обязанности другим членам команды. Это обеспечивает плавный переход и гарантирует, что проекты не останутся незавершенными. Мягкое завершение работы в приложении работает аналогичным образом, предоставляя достаточно времени для завершения текущих задач и правильной передачи ресурсов до завершения работы приложения.

Пример

Давайте создадим функцию с именем startHTTPServer для запуска нашего веб-сервера на порту 8080.

func startHTTPServer(ctx context.Context, wg *sync.WaitGroup) {
 defer wg.Done()

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

 // Start the HTTP server in a separate goroutine
 go func() {
  fmt.Println("Starting HTTP server...")
  err := server.ListenAndServe()
  if err != nil && err != http.ErrServerClosed {
   fmt.Printf("HTTP server error: %s\n", err)
  }
 }()

 // Wait for the context to be canceled
 select {
 case <-ctx.Done():
  // Shutdown the server gracefully
  fmt.Println("Shutting down HTTP server gracefully...")
  shutdownCtx, cancelShutdown := context.WithTimeout(context.Background(), 5*time.Second)
  defer cancelShutdown()

  err := server.Shutdown(shutdownCtx)
  if err != nil {
   fmt.Printf("HTTP server shutdown error: %s\n", err)
  }
 }()

 fmt.Println("HTTP server stopped.")
}

Когда сервер нужно выключить, мы используем метод server.Shutdown(), чтобы изящно завершить его работу. Метод Shutdown принимает контекст с тайм-аутом, чтобы позволить существующим подключениям завершить обработку перед закрытием. Это гарантирует, что сервер завершит свои текущие запросы перед выключением.

Когда приложение получает сигнал о завершении (например, SIGINT или SIGTERM), оно должно выполнить ряд шагов, чтобы обеспечить плавное завершение работы:

  1. Обработка сигналов: Golang предоставляет пакет signal, который позволяет обрабатывать сигналы ОС, такие как SIGINT (Ctrl+C) или SIGTERM (команда kill). Вы можете создать канал для прослушивания этих сигналов и запустить процесс корректного завершения работы.
  2. Канал выключения. Создайте канал для передачи сигнала выключения в различные части вашего приложения. Этот канал будет использоваться для уведомления различных горутин о завершении работы приложения.
  3. Логика очистки. Убедитесь, что все важные ресурсы (например, подключения к базе данных, сетевые подключения, файлы) должным образом закрыты и освобождены. Этого можно добиться, отложив функции очистки или используя операторы defer.
  4. Группа ожидания: используйте sync.WaitGroup, чтобы отслеживать запущенные горутины. Перед запуском горутины добавьте ее в группу ожидания, а по завершении удалите из группы ожидания. Таким образом, вы можете дождаться завершения всех горутин перед выходом из приложения.

Обратите внимание, что в нашей функции startHTTPServer мы получаем параметры context.Context и sync.WaitGroup. Давайте подробнее рассмотрим нашу функцию main, чтобы понять, как она работает.

package main

import (
 "context"
 "fmt"
 "os"
 "os/signal"
 "sync"
 "syscall"
)

func main() {
 // Create a context to handle graceful shutdown
 ctx, cancel := context.WithCancel(context.Background())

 // Create a WaitGroup to keep track of running goroutines
 var wg sync.WaitGroup

 // Start the HTTP server
 wg.Add(1)
 go startHTTPServer(ctx, &wg)

 // Listen for termination signals
 signalCh := make(chan os.Signal, 1)
 signal.Notify(signalCh, syscall.SIGINT, syscall.SIGTERM)

 // Wait for termination signal
 <-signalCh

 // Start the graceful shutdown process
 fmt.Println("\nGracefully shutting down HTTP server...")

 // Cancel the context to signal the HTTP server to stop
 cancel()

 // Wait for the HTTP server to finish
 wg.Wait()

 fmt.Println("Shutdown complete.")
}

Заключительная мысль

Точно так же, как важно дать людям закончить то, что они делают, прежде чем уйти, у компьютеров также есть вежливый способ остановить свои программы, называемый «мягким завершением работы». Это помогает программам правильно выполнять свои задачи и избегать проблем. Представьте, что вы говорите другу, что вы выйдете из комнаты после того, как он закончит свой игровой уровень. Таким образом, все работает гладко и не останавливается внезапно, как на компьютере, так и в реальной жизни. Внимательность и продуманность в своих действиях всегда приносит положительный результат!