Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

作ってよかったgraceful shutdownライブラリ #kyotogo

作ってよかったgraceful shutdownライブラリ #kyotogo

さっちゃん

December 02, 2023
Tweet

More Decks by さっちゃん

Other Decks in Programming

Transcript

  1. Goサブ会標準化分科會 • Goサブ會 ◦ 社内のエンジニアの有志が參加する ◦ Goに關して、動向を追ったり、相談する ◦ 毎週30分 •

    Goサブ會標準化分科會 ◦ Goサブ會の中から更に有志が參加する ◦ Goサブ會の實働部隊 ▪ libraryを調査・開發したり • 最近はboilerplateを減らそうと頑張ってゐる ▪ guidelineを考へたり
  2. 用語 • process ◦ OSのprocess • server ◦ 目的とする處理を行ふもの ▪

    mainから直接起動したい go routineが だいたい相當する ◦ 複數在りうる • server manager ◦ 起動や終了等・serverの狀態を管理する gracefulな起動・終了 server manager server server process
  3. gracefulな起動・終了 gracefulな起動 • 全てのserverがreadyになるのを待つ • serverが一つでも起動に失敗すれば、processを終了させる ◦ (※もっと柔軟にやりたい時はある …) gracefulな終了

    • 全てのserverが終了前處理を終へるのを待つ ◦ HTTP serverであれば、新しくrequestを受け附けないやうにし、處理中の requestが終了する迄待 つ ◦ 常駐processであれば、狀態を保存する • 但し、一定時閒でtimeoutし強制終了する
  4. Goでのgracefulな起動・終了 gracefulな起動 ctx := context.Background() ctx, stop := signal.NotifyContext(ctx, os.Interrupt)

    // SIGINTをtrapする defer stop() ctx, cancel := context.WithCancelCause(ctx) // google.golang.org/grpc の例 srv2 := grpc.NewServer() go func(ctx context.Context) { // 複數のserverを起動する爲にgo routineを分離する listener, err := net.Listen("tcp", ":0") if err != nil { cancel(err) // 起動に失敗したらprocessを終了する return } if err := srv2.Serve(listener); err != nil { cancel(err) // 起動に失敗したらprocessを終了する } }(ctx)
  5. Goでのgracefulな起動・終了 gracefulな終了 <-ctx.Done() // 終了signal、或いは起動失敗を待つ if err := context.Cause(ctx); err

    != nil && !errors.Is(err, context.Canceled) { // 起動に失敗したら異常終了する log.Fatalln(err.Error()) } ctx, cancelT := context.WithTimeout(context.Background(), 10 * time.Second) // timeout時刻を過ぎたら強制終了する。 Contextは作り直さねばならない defer cancelT() var wg sync.WaitGroup wg.Add(1) go func(ctx context.Context) { // 複數のserverをparallelに終了させる defer wg.Done() stopped := make(chan struct{}) go func() { srv2.GracefulStop() close(stopped) }() select { case <-stopped: case <-ctx.Done(): // timeoutしたら強制終了する } }(ctx) wg.Wait() // 全てのserverが終了するのを待つ if err := context.Cause(ctx); err != nil && !errors.Is(err, context.Canceled) { // graceful shutdownに成功したら正常終了、失敗したら異常終了する log.Fatalln(err.Error()) }
  6. Goでのgracefulな起動・終了 gracefulな終了 <-ctx.Done() // 終了signal、或いは起動失敗を待つ if err := context.Cause(ctx); err

    != nil && !errors.Is(err, context.Canceled) { // 起動に失敗したら異常終了する log.Fatalln(err.Error()) } ctx, cancelT := context.WithTimeout(context.Background(), 10 * time.Second) // timeout時刻を過ぎたら強制終了する。 Contextは作り直さねばならない defer cancelT() var wg sync.WaitGroup wg.Add(1) go func(ctx context.Context) { // 複數のserverをparallelに終了させる defer wg.Done() stopped := make(chan struct{}) go func() { srv2.GracefulStop() close(stopped) }() select { case <-stopped: case <-ctx.Done(): // timeoutしたら強制終了する } }(ctx) wg.Wait() // 全てのserverが終了するのを待つ if err := context.Cause(ctx); err != nil && !errors.Is(err, context.Canceled) { // graceful shutdownに成功したら正常終了、失敗したら異常終了する log.Fatalln(err.Error()) } やればできるが、code記述量が多い
  7. 「同じ問題」が發生してゐる • 踏みやすいbugを避けたい ◦ http.ErrServerClosed等、特定のerrorを無視しないといけない ◦ DoneしたContextを再利用してはいけない ◦ 終了signalをtrapし忘れる ◦

    shutdownのtimeoutを設定し忘れる ◦ 常に正常終了する | 常に異常終了する • 複數のserverを起動したい ◦ 一部のserverが起動に失敗したまま processが動き續けて欲しくない ◦ timeout期限內でparallelに終了させたい • 任意のserverを扱ひたい • net/httpやgRPC等、よく使ふserverは簡單に使ひたい
  8. 任意のserverを扱ふ場合 type ExampleBlockingServer struct{} // Serve methodとShutdown methodを實裝する func (s

    *ExampleBlockingServer) Serve(ctx context.Context) error { for { select { case <-ctx.Done(): return nil case <-time.After(time.Second): } } } func (s *ExampleBlockingServer) Shutdown(ctx context.Context) error { return nil } func main() { ctx := context.Background() srv := graceful.Servers{Servers: []graceful.Server{&ExampleBlockingServer{}}} // 複數のserverを扱へる if err := srv.Graceful(ctx, graceful.GracefulShutdownTimeout(time.Second)); err != nil { // 起動もしくは終了に失敗したら異常終了する panic(err) } }
  9. 單一のHTTP serverを扱ふ場合 ctx := context.Background() mux := http.NewServeMux() if err

    := gracefulhttp.ListenAndServe( // ListenAndServe! ctx, ":8000", mux, graceful.GracefulShutdownTimeout(time.Second), ); err != nil { panic(err) }
  10. 單一のgRPC serverを扱ふ場合 ctx := context.Background() opts := []grpc.ServerOption{} srv :=

    grpc.NewServer(opts...) reflection.Register(srv) if err := gracefulgrpc.ListenAndServe( // ListenAndServe! ctx, ":4317", srv, graceful.GracefulShutdownTimeout(time.Second), ); err != nil { panic(err) }