go x/net/http2: Server-side processing delay on one request will block other unrelated requests

zwghvu4y  于 2个月前  发布在  Go
关注(0)|答案(9)|浏览(86)

我们的应用在将结果上传回服务器时遇到了意外的停滞。经过一些调查,确定这可能是HTTP/2流控制中的实现错误。RFC 7540 第2节第5页明确指出:
通过将每个HTTP请求/响应交换与自己的流关联来实现请求的多路复用(第5节)。流彼此之间大致独立,因此被阻塞或停滞的请求或响应不会阻止其他流上的进展。
但实际上很容易使服务器停滞,有时甚至永久性地停滞。这个bug具有拒绝服务的可能性。
这个问题以前已经报告过,编号为#40816,仍然处于打开状态。

您使用的Go版本是什么(go version)?

go1.18.1

这个问题是否在最新版本中重现?

不知道,在1.19中可以重现。

您采取了哪些措施?

下面的程序演示了这个问题。同一个客户端向同一服务器的不同URL发送了三个请求。其中一个URL有一个硬编码的5秒“处理”延迟。每个请求向服务器发送一个10MB的虚拟有效载荷。在执行程序后,所有三个请求都将停滞5秒。
如果是#40816,处理延迟是一个共享锁,导致服务器范围内的死锁(潜在的拒绝服务)。
您需要为程序提供自己的(自签名的)SSL证书才能使其正常工作。

but5z9lq

but5z9lq1#

@neild@tombergan per owners

xggvc2p6

xggvc2p62#

好的,从我找到的错误来看,它看起来是这样的:
一开始,数据从3个客户端通过3个流发送,但只有2个流的数据被定期读取(在这种情况下,剩下的一个流等待5秒)。承载这3个流的连接对传入数据有一定的容量。客户端可以发送的数据量由服务器发送的窗口更新帧定义。每次读取一部分数据时,这些帧就会被发送。

问题出现在go/src/net/http/h2_bundle.go文件的第6325行和第6332行。当从请求体中读取数据时,可能会出现以下错误:

n, err = b.pipe.Read(p)
b.conn.noteBodyReadFromHandler(b.stream, n, err)

当其中一个流的数据没有被消耗时,连接的流入容量会缩小。结果是,最后一个可能通过连接发送的部分数据是由服务器没有被消耗的流发送的,服务器正在等待流入容量再次增长,但这只能在处理等待处理程序的流上完成。最后,当等待结束时,从“等待”流发送的数据被读取,服务器再次发送窗口更新帧。

cxfofazt

cxfofazt3#

很高兴能有一些机制来检查流发送的数据是否被读取。我担心为每个可能的流保留一部分连接容量既不是最优的,也不是符合RFC的(对此并不确定)。我只是在思考,并不知道在其他语言中这种情况的常见方法是什么。

izkcnapc

izkcnapc4#

是的,根据协议规范,客户端应尊重服务器设置的窗口大小。因此,我猜测由于服务器的接收窗口已满,客户端耗尽了“信用”,停止发送任何进一步的数据。由于没有更多数据供服务器处理,一切都停滞不前。
根据RFC,WINDOW_UPDATE帧允许为整个连接以及每个流设置窗口大小。因此,服务器应该能够调整连接和流窗口大小,以便客户端能够继续发送非阻塞流的数据。
根据您的评论,看起来服务器没有正确调整窗口大小。它应该减少阻塞流的窗口大小,以防止客户端发送未处理的数据。然后,连接窗口大小也应增加,以便客户端能够为非阻塞流发送数据。
或者,服务器需要为每个阻塞流缓冲直到最大窗口大小,并增加连接窗口大小,以便客户端可以为非阻塞流发送更多数据。

syqv5f0l

syqv5f0l5#

你提议的是每次创建新流时,增加连接窗口的大小?结果是连接窗口的大小将是所有流窗口大小的总和吗?

ndasle7k

ndasle7k6#

这是一种直接解决问题的方法,是的。但至少在读者(服务器上的)饥饿的情况下,可以增加连接窗口大小。

t30tvxxf

t30tvxxf7#

好的,我会尽力想出一些东西。

wlwcrazw

wlwcrazw8#

我重新思考了一下你的想法。如果我正确理解了RFC 7540的第6.9.3节,那么减小窗口大小会影响到所有流,因此其他请求也会受到影响。或者,我们可以为所有流减小窗口大小,如果其中一个流被填满,但我不喜欢这种方法或者可能我遗漏了什么。

我的建议如下:
如果一个流和连接的入站窗口大小都为0,那么我们可以通过某个值(可能是默认的初始流量65535?)来增加连接的窗口大小。此外,我们标记流,当服务器读取流缓冲区时,它会减少窗口大小。我认为如果缓冲区被读取,那么流就是未阻塞的,将其连接恢复到初始状态可能会更好。

jhkqcmku

jhkqcmku9#

另一方面,如果有多个流被阻塞,那么连接窗口大小将不得不增加几次,而上述条件将无法生效。
这将需要保持阻塞流的总数,如果它等于连接的最大窗口大小,那么我们就知道何时增加窗口。

相关问题