你使用的Go版本是什么( go version
)?
$ go version
go 1.14.7
这个问题在最新版本中是否重现?
待定。
你使用的操作系统和处理器架构是什么( go env
)?
Debian/Buster(服务器)
Linux Mint(客户端)
你做了什么?
这是Kopia(备份工具)的用户报告的。我无法亲自重现这个问题,但有两个用户分别独立报告了这个问题。
有一个客户端和服务器(HTTP+TLS),都使用golang 1.14.7进行编译。客户端使用PUT方法在1-4个并行goroutines上向服务器发送大型二进制数据块(约20 MB)。
有时服务器会无缘无故地卡住处理请求。当我们发生这种情况时,我们已经捕获了堆栈跟踪:
(服务器) kopia/kopia#538 (评论)
(客户端) kopia/kopia#538 (评论)
在堆栈跟踪中,你可以看到服务器在读取请求体上的ioutil.ReadAll()时卡住了3分钟:
goroutine 23781 [sync.Cond.Wait, 3 minutes]:
runtime.goparkunlock(...)
/home/travis/.gimme/versions/go1.14.7.linux.amd64/src/runtime/proc.go:310
sync.runtime_notifyListWait(0xc000326528, 0x16)
/home/travis/.gimme/versions/go1.14.7.linux.amd64/src/runtime/sema.go:513 +0xf8
sync.(*Cond).Wait(0xc000326518)
/home/travis/.gimme/versions/go1.14.7.linux.amd64/src/sync/cond.go:56 +0x9d
net/http.(*http2pipe).Read(0xc000326510, 0xc005779b50, 0x7e2b0, 0x7e2b0, 0x0, 0x0, 0x0)
/home/travis/.gimme/versions/go1.14.7.linux.amd64/src/net/http/h2_bundle.go:3515 +0x8f
net/http.(*http2requestBody).Read(0xc00117e7e0, 0xc005779b50, 0x7e2b0, 0x7e2b0, 0x12, 0x0, 0x0)
/home/travis/.gimme/versions/go1.14.7.linux.amd64/src/net/http/h2_bundle.go:5876 +0xa0
bytes.(*Buffer).ReadFrom(0xc0001136c0, 0x7fb0a5e23020, 0xc00117e7e0, 0x7fb0a5e23020, 0x9, 0xf8bc01)
/home/travis/.gimme/versions/go1.14.7.linux.amd64/src/bytes/buffer.go:204 +0xb1
io/ioutil.readAll(0x7fb0a5e23020, 0xc00117e7e0, 0x200, 0x0, 0x0, 0x0, 0x0, 0x0)
/home/travis/.gimme/versions/go1.14.7.linux.amd64/src/io/ioutil/ioutil.go:36 +0xe3
io/ioutil.ReadAll(...)
/home/travis/.gimme/versions/go1.14.7.linux.amd64/src/io/ioutil/ioutil.go:45
github.com/kopia/kopia/internal/server.(*Server).handleContentPut(0xc0000cc4d0, 0x12e4380, 0xc00117e8a0, 0xc002a2b900, 0x7fb0a5c80658, 0x1030ea0, 0xf22842676f7cbc01)
客户端代码如下:
https://github.com/kopia/kopia/blob/923c91b5a45f48a042569bc38d0de26d1caa7377/internal/apiclient/apiclient.go#L44
根据bug报告,禁用HTTP/2使用 GODEBUG
可以解决这个问题。
全面披露:我是Googler,但Kopia是我的个人项目,与Google无关。
7条答案
按热度按时间5rgfhyps1#
我进行了更多的调查,发现服务器此时正在处理5个请求:
goroutine
23751
-持有读锁 - 在ioutil.ReadAll()
内部goroutine
23781
-持有读锁 - 在ioutil.ReadAll()
内部goroutine
23888
-等待读锁 - 尚未执行ioutil.ReadAll()
goroutine
23939
-等待读锁 - 尚未执行ioutil.ReadAll()
goroutine
24018
-等待读锁 - 尚未执行ioutil.ReadAll()
goroutine
34
-等待读写锁我的理解是,由于RWMutex是偏向写入的,因此
34
在23751
和23781
完成之前不会获得读写锁,并且还会阻止24018
、23939
和23888
获取读锁。现在我在想,是否有3个请求(因为它们尚未获得共享锁)不能立即读取请求体,这是否会阻止已经获得共享锁的其他2个请求继续进行,特别是考虑到请求体相当大,每个约为20MB。
换句话说 - HTTP/2是否要求所有请求处理器都读取请求体以确保其活跃性?似乎HTTP/1.1服务器没有这种行为(我猜是因为没有请求管道)。
vyu0f0g12#
cc @fraenkel
fxnxkyjh3#
请注意 - 我们已经修改了Kopia中的代码,在获取共享锁之前读取所有请求负载,并得到了确认,这解决了问题。
如果确实需要实时性来读取请求体,那么也许应该进行记录。
nzkunb0c4#
/cc @bradfitz@tombergan per owners .
xytpbqjk5#
hi @jkowalski , were you able to find any workarounds for this problem? I'm having it pretty consistently too with http2
8mmmxcuj6#
请查看他上面的最后一条评论。
35g0bw717#
哦,我误解了问题,谢谢。