Golang运行时:epollwait在fd 3上失败并返回错误码9,这是Go的bug吗?
Golang运行时:epollwait在fd 3上失败并返回错误码9,这是Go的bug吗? 朋友们好!我是论坛的新手,很高兴能接受关于如何参与并成为一名优秀会员的指导。我遇到了一个问题,想请教大家的建议。
我在 Ubuntu 18.04.5 LTS 上运行 go version go1.14.1 linux/amd64,内核版本为 5.8.0-41-generic。我使用普通的 go build 进行构建,没有特殊的环境设置。我的程序在运行时栈上引发了一个运行时错误,原因是 epollwait() 失败并返回 EBADF。我可能误读了回溯信息,但我认为这并非由程序代码中的 goroutine 引起。通常,在 panic() 的转储信息中,我甚至看不到 runtime stack 部分。
untime: epollwait on fd 3 failed with 9
fatal error: runtime: netpoll failed
runtime stack:
runtime.throw(0xa18a04, 0x17)
/usr/local/go/src/runtime/panic.go:1114 +0x72
runtime.netpoll(0x0, 0x0)
/usr/local/go/src/runtime/netpoll_epoll.go:123 +0x363
runtime.findrunnable(0xc000034000, 0x0)
/usr/local/go/src/runtime/proc.go:2126 +0xc60
runtime.schedule()
/usr/local/go/src/runtime/proc.go:2520 +0x2fc
runtime.park_m(0xc000001680)
/usr/local/go/src/runtime/proc.go:2690 +0x9d
runtime.mcall(0x0)
/usr/local/go/src/runtime/asm_amd64.s:318 +0x5b
goroutine 1 [select]:
net/http.(*Transport).getConn(0xc0000eb540, 0xc00012abd0, 0x0, 0xc0000d86c0, 0x5, 0xc0000d8700, 0x13, 0x0, 0x0, 0x0, ...)
/usr/local/go/src/net/http/transport.go:1291 +0x57b
net/http.(*Transport).roundTrip(0xc0000eb540, 0xc0000c5300, 0xc0000a6d60, 0xc00011b1f0, 0x40e488)
/usr/local/go/src/net/http/transport.go:552 +0x726
net/http.(*Transport).RoundTrip(0xc0000eb540, 0xc0000c5300, 0xc0000eb540, 0x0, 0x0)
/usr/local/go/src/net/http/roundtrip.go:17 +0x35
net/http.send(0xc0000c5300, 0xad9e40, 0xc0000eb540, 0x0, 0x0, 0x0, 0xc0000b4140, 0xc, 0x1, 0x0)
/usr/local/go/src/net/http/client.go:252 +0x43e
net/http.(*Client).send(0xc00012a900, 0xc0000c5300, 0x0, 0x0, 0x0, 0xc0000b4140, 0x0, 0x1, 0xd)
/usr/local/go/src/net/http/client.go:176 +0xfa
[... down through Hashicorp Vault client and application code ...]
main.main()
/home/jmarks1/projects/20200124-loadvac/src/load_vac/load_vac.go:31 +0x53
goroutine 19 [chan receive]:
net/http.(*persistConn).addTLS(0xc0000c7440, 0xc0000d8700, 0xf, 0x0, 0xc0000d8710, 0x3)
/usr/local/go/src/net/http/transport.go:1459 +0x1d3
net/http.(*Transport).dialConn(0xc0000eb540, 0xae4d40, 0xc0000b6de0, 0x0, 0xc0000d86c0, 0x5, 0xc0000d8700, 0x13, 0x0, 0xc0000c7440, ...)
/usr/local/go/src/net/http/transport.go:1529 +0x1c5d
net/http.(*Transport).dialConnFor(0xc0000eb540, 0xc0000e0580)
/usr/local/go/src/net/http/transport.go:1365 +0xc6
created by net/http.(*Transport).queueForDial
/usr/local/go/src/net/http/transport.go:1334 +0x3fe
goroutine 5 [runnable]:
encoding/base64.(*Encoding).Decode(0xc0000ba000, 0xc000397200, 0x5d6, 0x5d6, 0xc00046601c, 0x7ca, 0x9e5, 0x0, 0x200, 0x0)
/usr/local/go/src/encoding/base64/base64.go:471 +0x744
encoding/pem.Decode(0xc000466000, 0x801, 0xa01, 0x801, 0xa01, 0x0, 0x0)
/usr/local/go/src/encoding/pem/pem.go:168 +0x766
crypto/x509.(*CertPool).AppendCertsFromPEM(0xc00007e780, 0xc000466000, 0x801, 0xa01, 0xa01)
/usr/local/go/src/crypto/x509/cert_pool.go:131 +0x64
crypto/x509.loadSystemRoots(0x0, 0x7f1f0936ca88, 0xc000117628)
/usr/local/go/src/crypto/x509/root_unix.go:75 +0x504
[... through X509 and associated code ...]
crypto/tls.(*Conn).clientHandshake(0xc000050e00, 0x0, 0x0)
/usr/local/go/src/crypto/tls/handshake_client.go:206 +0x5ef
crypto/tls.(*Conn).Handshake(0xc000050e00, 0x0, 0x0)
/usr/local/go/src/crypto/tls/conn.go:1340 +0xcc
net/http.(*persistConn).addTLS.func2(0x0, 0xc000050e00, 0xc0000200f0, 0xc0000740c0)
/usr/local/go/src/net/http/transport.go:1453 +0x42
created by net/http.(*persistConn).addTLS
/usr/local/go/src/net/http/transport.go:1449 +0x1aa
这是一个 Hashicorp Vault 客户端程序,这个问题似乎只在尝试向 Vault 发送特定数据时发生,并且仅针对某一个特定的 Vault 服务器。我推测可能是服务器版本不喜欢这个输入,或者……服务器线程/进程崩溃了……并在传输过程中断开了连接。(我不确定,也无法直接访问服务器。)
有趣的是,我无法从这个特定的 panic 中 recover()。在简单的主函数中,当 panic 发生时,延迟执行的函数没有被调用。如果我取消注释标记的那行 panic 代码(这绕过了 epollwait() 引起的 panic),这个 panic 是可以被恢复的。
func main() {
defer func() {
fmt.Fprintln(os.Stderr, "DEFERRED FUNC RUNNING")
if rval := recover(); rval != nil {
errQuit(errors.Errorf("%s", rval), "Failed")
}
}()
// 如果我取消注释下面这行,延迟方法就会运行
// panic("fooooo")
if err := cmd.RootCmd.Execute(); err != nil {
errQuit(err, "Failed")
}
}
这种“无法恢复的魔法 panic”看起来像是 Golang 的 bug,还是仅仅是一种程序 bug?为什么这个 panic 无法恢复?从这些代码片段中,您能看出我是否做错了什么吗?
非常感谢您的帮助!
更多关于Golang运行时:epollwait在fd 3上失败并返回错误码9,这是Go的bug吗?的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于Golang运行时:epollwait在fd 3上失败并返回错误码9,这是Go的bug吗?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
这不是Go语言的bug,而是你的程序在文件描述符管理上出现了问题。错误码9对应EBADF(Bad file descriptor),说明epoll正在监听一个无效的文件描述符。
问题通常发生在以下情况:
- 网络连接被意外关闭
- 文件描述符被错误地重用
- 并发访问导致竞态条件
从堆栈跟踪看,问题出现在net/http.(*Transport).getConn期间,这通常与HTTP连接管理有关。以下是一些可能的原因和解决方案:
1. 连接泄露或过早关闭
// 确保正确关闭响应体
resp, err := http.Get("http://example.com")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close() // 必须关闭
body, err := io.ReadAll(resp.Body)
// 处理响应
2. 并发访问问题
// 使用sync.Once确保Transport只创建一次
var (
transport *http.Transport
once sync.Once
)
func getTransport() *http.Transport {
once.Do(func() {
transport = &http.Transport{
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
}
})
return transport
}
3. 自定义Transport配置
transport := &http.Transport{
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
client := &http.Client{
Transport: transport,
Timeout: 30 * time.Second,
}
4. 关于panic无法恢复的原因
运行时错误(如throw触发的panic)无法通过recover()捕获。这是Go的预期行为:
func main() {
defer func() {
// 这无法捕获runtime.throw()产生的panic
if r := recover(); r != nil {
fmt.Println("Recovered:", r)
}
}()
// 这种panic可以被恢复
panic("normal panic")
// 但runtime.throw()产生的panic无法被恢复
}
5. 诊断文件描述符问题
// 检查当前进程打开的文件描述符
func checkFDs() {
pid := os.Getpid()
dir := fmt.Sprintf("/proc/%d/fd", pid)
files, err := os.ReadDir(dir)
if err != nil {
log.Printf("无法读取fd目录: %v", err)
return
}
log.Printf("进程 %d 打开了 %d 个文件描述符", pid, len(files))
}
建议的排查步骤:
- 升级到更新的Go版本(1.14.1较旧)
- 检查是否有并发访问共享的http.Client
- 确保所有响应体都被正确关闭
- 使用
ulimit -n检查文件描述符限制 - 考虑使用连接池管理HTTP客户端
这个问题最可能的原因是HTTP连接在epoll仍在监听时被意外关闭,导致文件描述符无效。检查你的Vault客户端代码,确保连接管理正确,特别是处理错误和清理资源的部分。

