はじめに

こちらはKyash Advent Calendar 2022 の 13 日目の記事です。

今年の 11 月に Kyash に入社しました!サーバサイドチームのueharaです👋

今回はnet/httpパッケージの非推奨メソッドであるTemporary()について、社のメンバーから知見を共有してもらったのでその話をします。

net/http パッケージの 非推奨メソッド Temporary() について

Temporary()については、フューチャー社の記事にわかりやすくまとめられています。

https://future-architect.github.io/articles/20220203a/

上記の記事を踏まえて、ここでは非推奨になった経緯と対応について言及しようと思います。

サッと概要を話すと、Temporary()net.Errorインターフェースに定義されているメソッドで、一時的なエラーかどうか判定するために用意されています。 ただし、「一時的」というのがうまく定義されていないとの理由で、こちらのメソッドは Go1.18 で非推奨になりました。

net.Error.Temporary has been deprecated. https://tip.golang.org/doc/go1.18

Temporary()が非推奨になった経緯

前提として、net.Errorインターフェースは、以下のように定義されています ※ソースは Go 1.19 です

// An Error represents a network error.
type Error interface {
	error
	Timeout() bool // Is the error a timeout?

	// Deprecated: Temporary errors are not well-defined.
	// Most "temporary" errors are timeouts, and the few exceptions are surprising.
	// Do not use this method.
	Temporary() bool
}

非推奨になったときの issue を追ってみます

https://github.com/golang/go/issues/45729

issue によると、Temporary()を実装していて、かつ true を返している標準パッケージのメソッドは以下の2パターンになります。

パターン 1: Timeout 系のエラーだけど、 Temporary() メソッドで true を返す

  • context: context.DeadlineExceeded
  • crypto/tls: All Dial timeouts.
  • net: Various timeouts.
  • net/http: Timeout when reading headers or bodies. (The error type is named httpError, but it is only used for timeouts.)
  • net/http: Also, HTTP/2 timeout reading response headers.
  • os: os.ErrDeadlineExceeded (defined in internal/poll)

パターン 2: Timeout 系ではないエラーで Temporary()true を返す(本来こっちだけの想定)

つまり、実際に「一時的なエラー」とみなされるエラーは、以下のようなシステムコールエラーとのことです。

  • ECONNRESET
  • ECONNABORTED
  • EAI_AGAIN
  • EINTR
  • EMFILE
  • ENFILE
  • EAGAIN
  • EWOULDBLOCK
  • EBUSY
  • ETIMEDOUT

結論として、Timeout 系のエラーなのにTemporary()trueを返しているパターンを除くと、本来のTemporary()は少数のシステムコールエラーによるもの(few exceptions are surprising)である。しかし、前者をカウントしていることにより、Temporary()trueになるパターンが頻繁に発生してるように見えるため、Temporary()の定義が明確でないから非推奨にしたほうがいいんじゃね、とのことでした。

linter でも非推奨であることを警告されます

社のメンバーから共有してもらうきっかけになった問題です。

以下のコードは、net.Errorインターフェースを満たし、Temporary()をつかうサンプルです

package main

import (
	"fmt"
	"net"
)

type MyNetError struct{}

func (m MyNetError) Error() string   { return "my net error" }
func (m MyNetError) Timeout() bool   { return false }
func (m MyNetError) Temporary() bool { return true }

var _ net.Error = &MyNetError{}

func myFunc() error { return MyNetError{} }

func main() {
	if ne, ok := myFunc().(net.Error); ok && ne.Temporary() {
		fmt.Println(ne.Error())
	}
}

このプログラムを linter でチェックすると、以下のように警告が出ます。 linter のランナーに、golangci-lintをつかいます。

$ golangci-lint run
net.go:19:44: SA1019: ne.Temporary has been deprecated since Go 1.18 because it shouldn't be used: Temporary errors are not well-defined. Most "temporary" errors are timeouts, and the few exceptions are surprising. Do not use this method. (staticcheck)
        if ne, ok := myError().(net.Error); ok && ne.Temporary() {

この警告を回避するために、2つの方法が挙げられます。

回避方法1: Temporary() をつかわない

シンプルにTemporary()を消してしまうパターンです

	if ne, ok := myError().(net.Error); ok {
		fmt.Println(ne.Error())
	}

ただし、標準パッケージではつかわれている箇所もあり、(限定的な)代替案についても議論されています。

回避方法 2: linter のチェックをしない

linter のチェックを行わないよう、ディレクティブを設定します。

golangci-lint で linter のチェックをスキップする

https://golangci-lint.run/usage/false-positives/#nolint-directive

	//nolint:staticcheck
	if ne, ok := myError().(net.Error); ok && ne.Temporary() {
		fmt.Println(ne.Error())
	}

直接 staticcheck を実行する

//lint:ignoreディレクティブをつかいます。

	//lint:ignore SA1019 no problem, thanks
	if ne, ok := myError().(net.Error); ok && ne.Temporary() {
		fmt.Println(ne.Error())
	}

さいごに

Temporary()で判定したいようなケースはなるべくさけて代替のエラーを見つけるのがよさそうですね

学びとしては、非推奨になったきっかけの issue を読んでみて、標準パッケージを読むことに対する抵抗が少しなくなったような気がしたことです💦