Goでgraceful shutdownをミニマムに書く
graceful shutdownの仕組みがわかってなかったので自分でミニマムに実装してみる。
コード量が小さく読みやすかったので以下のパッケージを参考にした。
実装
自分でミニマムに実装したコードは以下の通り。アクティブな接続が返ってこなかった場合とかも考えなくてはいけないが、ここでは理解のため手元で動かして簡単に試す程度の実装をしている。
package main import ( "log" "net" "net/http" "os" "os/signal" "sync" "syscall" "time" ) func indexHandler(w http.ResponseWriter, r *http.Request) { time.Sleep(10 * time.Second) w.Write([]byte("Hello, middleware!\n")) } func main() { http.HandleFunc("/", indexHandler) srv := &http.Server{Addr: ":8888", Handler: http.DefaultServeMux} var wg sync.WaitGroup srv.ConnState = func(conn net.Conn, state http.ConnState) { switch state { case http.StateActive: log.Println("StateActive!!!") wg.Add(1) case http.StateIdle: log.Println("StateIdle!!!") wg.Done() } } l, err := net.Listen("tcp", ":8888") if err != nil { log.Fatal(err) } waitSignal(l) err = srv.Serve(l) wg.Wait() if err != nil { log.Fatal(err) } } func waitSignal(l net.Listener) { c := make(chan os.Signal, 1) signal.Notify(c, syscall.SIGINT) go func() { sig := <-c switch sig { case syscall.SIGINT: signal.Stop(c) l.Close() } }() }
動作確認すると、
// server $ go run test.go // サーバ起動 // client $ curl localhost:8888 // curl実行 // server $ go run test.go 2017/12/20 11:10:03 StateActive!!! 2017/12/20 11:10:13 StateIdle!!! // 特に何もしない // client $ curl localhost:8888 Hello, middleware! // 正常にレスポンスが返る // client $ curl localhost:8888 Hello, middleware! $ curl localhost:8888 // 再度curl実行 // server $ go run test.go 2017/12/20 11:10:03 StateActive!!! 2017/12/20 11:10:13 StateIdle!!! 2017/12/20 11:11:59 StateActive!!! ^C // Ctrl+CでSIGINTを送る // client $ curl localhost:8888 Hello, middleware! $ curl localhost:8888 Hello, middleware! // 正常にレスポンスが返る // server $ go run test.go 2017/12/20 11:10:03 StateActive!!! 2017/12/20 11:10:13 StateIdle!!! 2017/12/20 11:11:59 StateActive!!! ^C2017/12/20 11:12:09 StateIdle!!! // 既に確立されたコネクションは正常にレスポンスを返している 2017/12/20 11:12:09 accept tcp [::]:8888: use of closed network connection exit status 1 // 確立されたコネクションが全てレスポンスを返してからサーバシャットダウンしている
感想
ポイントは
- プロセスを終了するシグナルを
signal.Notify
で捕まえて処理を変更すること http.StateActive
な接続が存在する場合はプロセスがすぐに終了しないようにすること
だろうか。最初はリスナーをクローズすると確立されたConnも終了してしまうのかと思っていたが、ここに関してはTCPListener.Close
がうまい感じにやってくれている。このためリスナーをクローズしたタイミングで新規の接続は受け付けないといったことも実現できている。
https://golang.org/pkg/net/#TCPListener.Close
Close stops listening on the TCP address. Already Accepted connections are not closed.