【Go】HTTPサーバがexitする直前に、後始末処理を仕込む!(コネクション開放とか)


はい

今回ご紹介するのは、こちらのnasa9084/sygです。
https://github.com/nasa9084/syg

これを使えば、HTTPサーバがexit(停止)する前に、任意のコードを仕込めてしまいます。
ありがてぇ…ありがてぇ…

経緯

GoでHTTPのRESTAPIサービスを作っていて、その中でRedisのコネクションをプールして使いまわす機能を作りました。
そうするとやっぱ、サービスが終了(HTTPサーバが停止)するときにコネクションをちゃんと開放しなきゃですよ。

「でも、http.ListenAndServeを実行してるところにdefer funcを仕掛けりゃいいだけッスよね? ちょろいちょろい♪」

とナメていたら…

defer funcが実行されない!!

どうやらos.Exit(code int)が発動すると、deferがかき消されてしまうとのこと。なんてこったい

そこで見つけたのがこちらになります。

用例

例えばこんな感じで、シグナルを受け取ったときに発動するコールバック関数に、好きな後始末処理を仕込むだけ

私の場合は、プールしてるRedisのコネクションをClose()してます。

package main

import (
    "context"
    "fmt"
    "github.com/go-redis/redis"
    "net/http"
    "os"
    "github.com/nasa9084/syg"
)

var pooledKVSConnectionsMap = make(map[*redis.Options]*redis.Client)

func main() {
    s := &http.Server{Addr: ":8080"}
    waitCh := make(chan struct{})

    cancel := syg.Listen(func(os.Signal) {
        s.Shutdown(context.Background())
        destruct() // <--- 仕込み!
        close(waitCh)
    }, os.Interrupt)
    defer cancel()

    if err := s.ListenAndServe(); err != http.ErrServerClosed {
        fmt.Print("listening server error:" + err.Error())
    }
    <-waitCh
}

func destruct() {
    for _, kvsCon := range pooledKVSConnectionsMap {
        cerr := kvsCon.Close()
        if cerr != nil {
            fmt.Print(cerr)
        } else {
            fmt.Print(kvsCon, " ... closed.")
        }
    }
}

ご注意

github.com/nasa9084/sygREADME.mdがちょっと古いのか、そちらの例ではdefer cancel()のコードが抜けているので、作者様サイトの例コードやsyg_example_test.goなどを参照されると良いかと思います。

さいごに

・・・
というか、そもそも間違ってました・・・

いえ、この記事そのものではないのですが、go-redis/redisはあらかじめコネクションプールの仕組みを内包しており、使い手側は気にせずNewClient(), Close()をして良いそうです

実際goroutineを使ってNewClient(), Close()を毎度呼ぶコードと比較したのですが、全く問題ありませんでした。

・・・まあ、勉強になったからよし!