Open balloob opened 3 years ago
Full reproducer
from aiohttp import web
from async_timeout import timeout
import asyncio
from multidict import CIMultiDict, CIMultiDictProxy, istr
from aiohttp.test_utils import make_mocked_request
from unittest import mock
import aiosignal
from functools import partial
def make_request(method, path, headers=None, protocols=False):
app = ret = mock.Mock()
app.loop = loop
app._debug = False
app.on_response_prepare = aiosignal.Signal(ret)
app.on_response_prepare.freeze()
protocol = mock.Mock()
protocol.set_parser.return_value = ret
if headers is None:
headers = CIMultiDict(
{
"HOST": "server.example.com",
"UPGRADE": "websocket",
"CONNECTION": "Upgrade",
"SEC-WEBSOCKET-KEY": "dGhlIHNhbXBsZSBub25jZQ==",
"ORIGIN": "http://example.com",
"SEC-WEBSOCKET-VERSION": "13",
}
)
if protocols:
headers["SEC-WEBSOCKET-PROTOCOL"] = "chat, superchat"
req = make_mocked_request(
method, path, headers, app=app, protocol=protocol, loop=app.loop
)
return req
async def repro():
request = make_request("GET", "/ws")
wsock = web.WebSocketResponse()
# Simulate `WebSocketResponse._start` taking a long time
async def _prepare_headers(*args, **kwargs):
await asyncio.sleep(10)
wsock._prepare_headers = _prepare_headers
try:
# Bug can happen when this job times out during `WebSocketResponse `calling `super().prepare()`
# and `StreamResponse` is inside `await self._start` and client connection is dropped but OS didn't notice
async with timeout(3):
await wsock.prepare(request)
except asyncio.TimeoutError:
assert wsock.prepared
if wsock.prepared:
# This raises because `close()` checks if `self._writer` is not None,
# which is set inside `StreamResponse._post_start`
await wsock.close()
async def run():
await repro()
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(run())
Describe the bug
WebSocketResponse
(web_ws.py
) inherits fromStreamResponse
(web_response.py
).WebSocketResponse
overridesprepare
with apre_start
andpost_start
, which setsself._writer
to aWebSocketWriter
.WebSocketResponse
does not override theprepared
property fromStreamResponse
prepared
property checks ifself._payload_writer is not None
To Reproduce
Expected behavior
If
wsock.prepared
returnsTrue
, it should be safe to close the connection.Logs/tracebacks
aiohttp Version
multidict Version
yarl Version
OS
Debian
Related component
Server
Additional context
No response
Code of Conduct