0時に寝て2時に起きて5時に起きて6時に起きた。久しぶりに夢をみたような気がする。

go の channel とクローズ制御

rabbitmq/amqp091-go を使った pubsub の consumer の実装で次のような for ループでメッセージを取得していた。この場合 deliveries の channel が閉じられると内側の for ループが終了して、外側の for ループの接続処理へ遷移する。

for {
    // コネクションの接続処理

    for d := range deliveries {
        // メッセージ処理
    }
}

この処理を context を使ってキャンセルできるよう、次に書き換えた。channel の Receive operator のドキュメントによると、channel は2値を返すことができて、戻り値の2番目の ok の値を調べることでその channel がクローズされているかどうかを判定できる。この仕組みを知っていれば select で ok を調べてエラー処理を実装できる。そうしないと deliveries の channel が閉じられれたときに select では常にゼロ値のメッセージが返るようになって意図したメッセージ処理とならない。

for {
    // コネクションの接続処理

    for {
        select {
        case <-ctx.Done():
            // context がキャンセルされたら終了処理
            if err := s.ch.Cancel(cid, false); err != nil {
                return fmt.Errorf("failed to cancel channel: %w", err)
            }
            return nil
        case d, ok := <-deliveries:
            if ok {
                // メッセージ処理
            } else {
                // エラー処理
                break
            }
        }

        // コネクションが閉じていればループを抜けて再接続
        if s.conn.IsClosed() || s.ch.IsClosed() {
            log.Info("the session was closed, try to reconnect", nil)
            break
        }
    }
}

同じ channel からメッセージを取り出すループ処理でも用途によって使い分けがあるなぁと今更ながら学んだ。というか、自分でバグを埋め込んで自分で直した (´・ω・`)

マージまで約1ヶ月

先日送った amqp091-go の pr が最終的にはほぼそのままマージされた。約1ヶ月ぐらいの議論やレビューがあって取り込まれた。新しいリリースバージョン (次は 1.9.0?) が出たらうちのアプリケーションでもこの新しいメソッドを使うようにして実績を積ませる。この pr は「あったら便利」程度の機能なのでそれほど重要ではない。

この成功体験をもって次は本命の go-ldap の pr のマージに向けて勢いをつける。こっちの pr は業務に直結しているので本気でやり取りしている。