🦈

Go言語のgRPCでcontext cancelがどのように伝搬されるかWiresharkで確認する

に公開

はじめに

gRPCのアプリケーションで通信をしている時、クライアント側でcontextがタイムアウトや明示的にキャンセルされたりすると、サーバ側でも引数で渡されたcontextが即時にキャンセルされるという挙動をします。
どういう仕組みでキャンセルされるのか気になったため調べてみたところ、

  1. クライアントのcontextがキャンセルされる
  2. クライアントがHTTP/2のRST_STREAMをサーバに送信する
  3. サーバでRST_STREAMを受け取ると該当streamのcontextをキャンセルする

という流れになっているみたいです。
以下の記事が大変参考になりました。
https://zenn.dev/bellwood4486/articles/grpc-cancel-timeout

ということで仕組みは理解できたので、この記事では勉強も兼ねて実際にRST_STREAMが送信されていることをパケットキャプチャで確認してみたいと思います。

実行環境

gRPCのhelloworldのコードをベースに少し改修を入れて動作確認をしていきます。
ソースコードはこちらにあります。

構成としては以下の通りです。

  • gRPCサーバ
    SayHelloAPIが呼ばれると3秒待機してレスポンスを返す。
  • gRPCクライアント
    1秒でタイムアウトを設定したcontextを入れてSayHelloAPIを呼び出す。

クライアントが1秒経過後にcontextをキャンセルしてRST_STREAMが送信されるはずです。

実行結果

クライアント側のログ

タイムアウトなのでDeadlineExceededのログが出力されています。

make run-client                              
go run main.go client
2025/05/25 16:33:08 could not greet: rpc error: code = DeadlineExceeded desc = context deadline exceeded
exit status 1
make: *** [run-client] Error 1

サーバ側のログ

リクエストを受けてから1秒後にcontextがキャンセルされていることが確認できます。

make run-server
go run main.go server
2025/05/25 16:33:07 Received: world
2025/05/25 16:33:08 Context done: context canceled

Wiresharkのログ

Wiresharkのキャプチャしている状態でサーバとクライアントの処理を実行します。
キャプチャされた内容を見ると、まず①でgRPCのリクエストが送信されていることが確認できます。
その後、約1秒後にRST_STREAMが2回送られていることが確認できます。
2回送られているのはクライアントとサーバそれぞれが送っているからです。

②のクライアントからサーバに送ったRST_STREAMの内容はこのようになっており、Error CodeがCANCELになっていることが確認できます。

おわりに

gRPCでcontextのキャンセルがどのように伝搬されているかネットワークのレイヤーから確認することができました。
gRPCではクライアントのcontextキャンセルをHTTP/2のRST_STREAMを介してサーバ側に伝搬していることがわかりました。
この挙動は特にgRPCサーバで引数のcontextを内部で使い回している場合、クライアントからのキャンセルにより意図せずサーバ側の処理が中断してしまう可能性があるため注意が必要です。
それを防ぐためサーバ側の処理で

等の実装を適切に行う必要があります。

GitHubで編集を提案

Discussion