はじめに
こちらはKyash Advent Calendar 2022 の 13 日目の記事です。
今年の 11 月に Kyash に入社しました!サーバサイドチームのueharaです
今回はnet/http
パッケージの非推奨メソッドであるTemporary()
について、社のメンバーから知見を共有してもらったのでその話をします。
net/http パッケージの 非推奨メソッド Temporary() について
Temporary()
については、フューチャー社の記事にわかりやすくまとめられています。
上記の記事を踏まえて、ここでは非推奨になった経緯と対応について言及します。
サッと概要を書くと、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 を追ってみます
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
を返す(本来こっちだけの想定)
- net: ECONNRESET and ECONNABORTED errors returned by accept().
- net: EAI_AGAIN errors returned by getaddrinfo().
- syscall/syscall_plan9.go, syscall/syscall_unix.go, syscall/syscall_js.go,syscall/syscall_windows.go:
EINTR, EMFILE, ENFILE, plus errors also considered timeouts: EAGAIN, EWOULDBLOCK, EBUSY, and ETIMEDOUT. (Some minor > variation between operating systems.)
つまり、実際に「一時的なエラー」とみなされるエラーは、以下のようなシステムコールエラーとのことです。
- 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 のチェックをスキップする
//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 を読んでみて、標準パッケージを読むことに対する抵抗が少しなくなったような気がしたことです
さいごに
Kyashでは多彩な職種で仲間を募集しております。
一緒にKyashを育てていきませんか?
興味がありましたらぜひ、カジュアル面談からでもご連絡お待ちしております!
募集職種一覧