理系学生日記

おまえはいつまで学生気分なのか

gRPC におけるタイムアウト時の挙動

gRPC には、当然ながらクライアントサイドでタイムアウトが設定できます。このとき、HTTP/2 上でどういうフレームが流れるのかを整理してみます。

タイムアウト設定

golang

Golang の場合は、 context.WithTimeout を使ってタイムアウトを指定する。

   ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()

    req := &pb.EchoRequest{Message: message}

node

node.js の場合は、CallOptions で指定することになります。

        const timeout = 2000 // milliseconds
        client.SayHello(
            { name: "kiririmode" },
            { deadline: Date.now() + timeout },
            (err, res) => {
                if (err) {
                    console.log(err)
                    return
                }
                console.log(res)
            }
        )

HTTP/2 での挙動

WireShark でパケットキャプチャをした結果が以下になります。 [1] となっており、今回の Unary Call は stream 1 が使われていることがわかります。

f:id:kiririmode:20190623083413p:plain

まず、リクエストヘッダを覗いて見ましょう。 f:id:kiririmode:20190623083604p:plain

Header フレームの中に grpc-timeout ヘッダが存在しており、その値が 2S となっていることがわかります。このように、gRPC におけるタイムアウトは Timeout → "grpc-timeout" TimeoutValue TimeoutUnit という形のヘッダとして表現されると定義されています。詳細は grpc/PROTOCOL-HTTP2.md at master · grpc/grpc · GitHub に記載があります。 今回はタイムアウトを 2 秒としているので、当該ヘッダの値は 2S (2秒) となっています。

そしておおよそ 2 秒後に、クライアントから RST_STREAM フレームが送信されています。

f:id:kiririmode:20190623084058p:plain

これにより、今回使用した stream 1 は closed state に遷移し、完全に stream としてのライフサイクルを終えたことがわかります。 stream の状態遷移については、RFC 7540 を参照してください。

                                +--------+
                        send PP |        | recv PP
                       ,--------|  idle  |--------.
                      /         |        |         \
                     v          +--------+          v
              +----------+          |           +----------+
              |          |          | send H /  |          |
       ,------| reserved |          | recv H    | reserved |------.
       |      | (local)  |          |           | (remote) |      |
       |      +----------+          v           +----------+      |
       |          |             +--------+             |          |
       |          |     recv ES |        | send ES     |          |
       |   send H |     ,-------|  open  |-------.     | recv H   |
       |          |    /        |        |        \    |          |
       |          v   v         +--------+         v   v          |
       |      +----------+          |           +----------+      |
       |      |   half   |          |           |   half   |      |
       |      |  closed  |          | send R /  |  closed  |      |
       |      | (remote) |          | recv R    | (local)  |      |
       |      +----------+          |           +----------+      |
       |           |                |                 |           |
       |           | send ES /      |       recv ES / |           |
       |           | send R /       v        send R / |           |
       |           | recv R     +--------+   recv R   |           |
       | send R /  `----------->|        |<-----------'  send R / |
       | recv R                 | closed |               recv R   |
       `----------------------->|        |<----------------------'
                                +--------+

          send:   endpoint sends this frame
          recv:   endpoint receives this frame

          H:  HEADERS frame (with implied CONTINUATIONs)
          PP: PUSH_PROMISE frame (with implied CONTINUATIONs)
          ES: END_STREAM flag
          R:  RST_STREAM frame

                          Figure 2: Stream States

まとめ

client で gRPC timeout を指定した場合、

  • client からは grpc-timeout ヘッダによってサーバにタイムアウト時間が通知される
  • 実際に client でタイムアウトすると、client から END_STREAM frame が送出され、stream は closed 状態に遷移し終了する