go x/net/http2: ioutil.ReadAll(req.Body) 在 HTTP/2 上卡住

0yg35tkg  于 1个月前  发布在  Go
关注(0)|答案(7)|浏览(38)

你使用的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无关。

5rgfhyps

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是偏向写入的,因此342375123781完成之前不会获得读写锁,并且还会阻止240182393923888获取读锁。
现在我在想,是否有3个请求(因为它们尚未获得共享锁)不能立即读取请求体,这是否会阻止已经获得共享锁的其他2个请求继续进行,特别是考虑到请求体相当大,每个约为20MB。
换句话说 - HTTP/2是否要求所有请求处理器都读取请求体以确保其活跃性?似乎HTTP/1.1服务器没有这种行为(我猜是因为没有请求管道)。

fxnxkyjh

fxnxkyjh3#

请注意 - 我们已经修改了Kopia中的代码,在获取共享锁之前读取所有请求负载,并得到了确认,这解决了问题。
如果确实需要实时性来读取请求体,那么也许应该进行记录。

nzkunb0c

nzkunb0c4#

/cc @bradfitz@tombergan per owners .

xytpbqjk

xytpbqjk5#

hi @jkowalski , were you able to find any workarounds for this problem? I'm having it pretty consistently too with http2

8mmmxcuj

8mmmxcuj6#

请查看他上面的最后一条评论。

35g0bw71

35g0bw717#

哦,我误解了问题,谢谢。

相关问题