你正在使用的Go版本是什么( go version
)?
$ go version
go version go1.21.0 linux/amd64
这个问题在最新版本的发布中是否重现?
是的
你正在使用什么操作系统和处理器架构( go env
)?
go env
输出
$ go env
GO111MODULE=''
GOARCH='amd64'
GOBIN='/home/eric/work/bin'
GOCACHE='/home/eric/.cache/go-build'
GOENV='/home/eric/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMODCACHE='/home/eric/work/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPATH='/home/eric/work'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/home/eric/go'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/home/eric/go/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.21.0'
GCCGO='gccgo'
GOAMD64='v1'
AR='ar'
CC='gcc'
CXX='g++'
CGO_ENABLED='1'
GOMOD='/dev/null'
GOWORK=''
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
PKG_CONFIG='pkg-config'
GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build978392017=/tmp/go-build -gno-record-gcc-switches'
你做了什么?
当调用下游 net/http.conn.serve
实现中的 panic 时, http.ErrAbortHandler
方法能够处理一个特殊情况,它会静默恢复并防止程序崩溃。
当链接 http.Handler
实现时,如果一个 http.Handler
使用了 golang.org/x/sync/singleflight.Group
,并且该 panic 下游的 http.Handler
使用 http.ErrAbortHandler
恐慌,那么 singleflight
包首先从恐慌中恢复,将恐慌值 Package 在其自己的私有错误类型中,并用其私有类型重新引发恐慌。当新的恐慌到达 net/http.conn.serve
时,进行直接比较以查看是否为 err != ErrAbortHandler
。由于技术上 err
不是 ErrAbortHandler
,而只是另一个 Package ErrAbortHandler
的错误类型,因此 net/http.conn.serve
不处理这个特殊情况,允许程序崩溃。
处理恐慌且程序不崩溃的示例:
使用 golang.org/x/sync/singleflight.Group
但不处理恐慌导致程序崩溃的示例:
你期望看到什么?
在使用链式 http.Handler
实现中,当下游调用一个 panic(http.ErrAbortHandler
时, net/http.conn.serve
能够解包错误以确定错误是否实际上是 http.ErrAbortHandler
并处理恐慌,而不使程序崩溃。
你看到了什么?
程序以消息 panic: net/http: abort Handler
崩溃。
4条答案
按热度按时间knpiaxh11#
https://go.dev/cl/526418提到了这个问题:
net/http: unwrap errors when checking for ErrAbortHandler
pw9qyyiw2#
CC @neild
nqwrtyyt3#
https://go.dev/cl/526171提到了这个问题:
singleflight: add panicError.Unwrap method
pftdvrlh4#
由于技术上 err 不是 ErrAbortHandler,而是另一种 Package ErrAbortHandler 的错误类型,net/http.conn.serve 没有处理这个特殊情况,允许程序崩溃。
net/http
会吞噬来自处理器的所有恐慌。ErrAbortHandler
只控制是否记录错误。如果程序崩溃,那么其他事情正在发生。在 https://go.dev/cl/526418 中
net/http
的变化对我来说似乎是合理的——我们检查ErrAbortHandler
,我认为没有理由不检查 PackageErrAbortHandler
的错误。singleflight
对恐慌的抽象是有漏洞的:如果传递给singleflight.Group.Do
的函数发生恐慌,那么Do
会传播恐慌,但它将原始恐慌值隐藏在一个添加堆栈跟踪的类型中。调用者无法恢复原始恐慌值。singleflight
没有说明如何处理恐慌。https://go.dev/cl/526171 建议将
singleflight
更改为 Package 原始恐慌值,当该值是一个错误时。这将允许恢复原始恐慌值,但仅限于该值是一个error
的情况。我对
error
值的特殊处理不确定;如果恐慌值不是错误,但调用者仍然需要它怎么办?另一方面,对于singleflight
为原始恐慌源添加堆栈信息似乎很有用,而当原始值是一个使用错误 Package 来增强错误的错误时,这种做法相当合理。