はじめに
TIG真野です。net/httpパッケージには非常にお世話になっています。Goの net/httpの内部にはサーバー/クライアントの両方が含まれていますが、今回はクライアントサイドの話です。
TCPレベルの接続エラーの調査のために標準パッケージやサードパーティのライブラリのコードを読み込んでいくと、Temporary() 関数だけをもった temporary インタフェースが登場します。HTTP周りでtemporaryと聞くと、 307 Temporary Redirect のステータスコードのことかと思いますが、ちょっと違いそうです。どういったものでどういった場合に出てくるのか、調べました。
Temporary()とは
Temporary()はnet/http パッケージなどのコードを見ていると出てくる関数です。プライベートなインタフェースがあちこちのパッケージや呼び出し元のライブラリでつくられています。
type temporary interface { |
例えば次のhttpErrorはtemporaryインタフェースを満たし、常にtrueを返すように実装されています。
type httpError struct { |
netパッケージのOpErrorもtemporaryインタフェースを満たし、Temporary()関数にはロジックが結構が入っています。
func (e *OpError) Temporary() bool { |
AWS SDK for GoにもorigiErrがtemporaryインタフェース(Temporary() boolの関数)を満たしていて、かつTemporary()の結果がtrueの場合はリトライする、みたいな実装がよくあります。
// AWS SDK for Goのretyer.goの例 |
どういったルールでtrue/falseになって、どのように使われるべきなんでしょうか。
Temporary() の使い方
go.devのError handling and Go にドンピシャな説明が書いてありました(本来はエラーハンドリングの説明ですが)。
package net |
このerrorは、Temporary()を呼ぶことで、一時的なネットワークエラーと永続的なネットワークエラーを区別するために用意されたようです。例えばWebクローラーは、一時的なエラーが発生したときにスリープして再試行し、それ以外の場合はあきらめるといった使い方に利用できるとのこと。サンプルコードも付いていました。
if nerr, ok := err.(net.Error); ok && nerr.Temporary() { |
ということで、Temporary() は日本語訳そのままで、一時的なエラー(リトライすると成功するかも)かどうかを区別するために用意されたものでした。例えばURLが無効であるとかクライアントサイドの指定の問題は、何回繰り返しても成功することは無いのでTemporary() はfalseを返すべきだということです。
例を探すとIPアドレスのパースに失敗したときに呼ばれるnetパッケージのParseErrorはTemporary()を常にfalseを返していました。
// A ParseError is the error type of literal network address parsers. |
最初に説明したhttpErrorはクライアント側で指定した時間に対してタイムアウトしたときに利用されていたため、再試行で成功する可能性があるためtrueが返されるのだと思います。OpErrorはシステムコール側の処理でのエラーハンドリング結果に移譲していますが、ECONNRESET(connection reset by peer)やアボートされたときはリトライの余地がありと判定しtrueを返しています。
もはや非推奨である
ここまでTemporary()について説明してきましたが、netパッケージのErrorでは(おそらく)Go 1.18からのように書かれます。Deprecated(非推奨)になります。
// An Error represents a network error. |
net: deprecate Temporary error status #45729に理由が書かれています。 Timeout()はわかりやすいけど、Temporary()は何が一時的で何が永続的なのかの区別が明確じゃなく、本来別の表現で区別されるものもTemporary()として扱われてしまっているのでは無いかということ。Timeout()で区別がつけるものはそちらを使いましょうということかと思います(これだとECONNRESET, ECONNABORTEDが表現できない気がしますが…)
ちなみに、os: remove ErrTemporary in Go 1.13 #32463 にあるように、 os.ErrTemporary
は削除されたようです。
Temporary()の判定方法
Temporary()の判定にはType Switchしたり、次のようなerrors.As()を使って判定することが多かったかと思います。
type temporary interface { Temporary() bool } |
この辺は標準パッケージ側でヘルパー関数を作ったら? という提案がproposal: errors: add new function Temporary(error) bool出ています。期待ですねと言いたいところですが、Temporary() の立ち位置自体が先程説明したようにちょっと微妙であるため、その結果次第ですがおそらく追加されることは無さそうです。
まとめ
- Temporary()は一時的なエラーであるかどうかを示し、リトライで成功する可能性がある場合にtrueを返す
- 例えば、タイムアウトやTCP通信でコネクションリセットなどを返されたときにtrueになる
- とは言え、Temporary()の使い分けのハッキリとした定義が難しく、位置づけがTimeout()と被ることもあり非推奨の方向で進んでいる