Go言語のgRPCでcontext cancelがどのように伝搬されるかWiresharkで確認する
はじめに
gRPCのアプリケーションで通信をしている時、クライアント側でcontextがタイムアウトや明示的にキャンセルされたりすると、サーバ側でも引数で渡されたcontextが即時にキャンセルされるという挙動をします。
どういう仕組みでキャンセルされるのか気になったため調べてみたところ、
- クライアントのcontextがキャンセルされる
- クライアントがHTTP/2の
RST_STREAM
をサーバに送信する - サーバで
RST_STREAM
を受け取ると該当streamのcontextをキャンセルする
という流れになっているみたいです。
以下の記事が大変参考になりました。
ということで仕組みは理解できたので、この記事では勉強も兼ねて実際にRST_STREAM
が送信されていることをパケットキャプチャで確認してみたいと思います。
実行環境
gRPCのhelloworldのコードをベースに少し改修を入れて動作確認をしていきます。
ソースコードはこちらにあります。
構成としては以下の通りです。
- gRPCサーバ
SayHello
APIが呼ばれると3秒待機してレスポンスを返す。 - gRPCクライアント
1秒でタイムアウトを設定したcontextを入れてSayHello
APIを呼び出す。
クライアントが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を内部で使い回している場合、クライアントからのキャンセルにより意図せずサーバ側の処理が中断してしまう可能性があるため注意が必要です。
それを防ぐためサーバ側の処理で
- contextを分ける
- WithoutCancelをかませる
等の実装を適切に行う必要があります。
Discussion