Closed
Description
Long story short
Websocket heartbeat sender task raises AttributeError when connecting with slow clients.
class WebSocketResponse(StreamResponse):
...
def _reset_heartbeat(self) -> None:
self._cancel_heartbeat()
if self._heartbeat is not None:
self._heartbeat_cb = call_later(
self._send_heartbeat, self._heartbeat, self._loop)
def _send_heartbeat(self) -> None:
if self._heartbeat is not None and not self._closed:
# fire-and-forget a task is not perfect but maybe ok for
# sending ping. Otherwise we need a long-living heartbeat
# task in the class.
self._loop.create_task(self._writer.ping()) # type: ignore
...
async def prepare(self, request: BaseRequest) -> AbstractStreamWriter:
...
protocol, writer = self._pre_start(request)
payload_writer = await super().prepare(request)
assert payload_writer is not None
self._post_start(request, protocol, writer)
...
def _pre_start(self, request: BaseRequest) -> Tuple[str, WebSocketWriter]:
self._loop = request._loop
headers, protocol, compress, notakeover = self._handshake(
request)
self._reset_heartbeat()
...
def _post_start(self, request: BaseRequest,
protocol: str, writer: WebSocketWriter) -> None:
self._ws_protocol = protocol
self._writer = writer
...I guess that the exception is caused by the race condition in the following steps
- In
WebSocketResponse._pre_start,WebSocketResponse._reset_heartbeatis called andWebSocketResponse._send_heartbeatis scheduled to be executed. - Because
await super().preparecan take a long time, evensuper().preparetask is not finished,WebSocketResponse._send_heartbeatcan be executed. - However,
self._writerwhich is used byWebSocketResponse._send_heartbeatis not initialized yet becauseself._post_startis not called yet.
Expected behaviour
The exception should not be raised.
Actual behaviour
Exception in callback <bound method WebSocketResponse._send_heartbeat of <WebSocketResponse Switching Protocols ...>>
handle: <TimerHandle WebSocketResponse._send_heartbeat>
Traceback (most recent call last):
File "uvloop/cbhandles.pyx", line 265, in uvloop.loop.TimerHandle._run
File "/usr/local/lib/python3.7/site-packages/aiohttp/web_ws.py", line 101, in _send_heartbeat
self._loop.create_task(self._writer.ping()) # type: ignore
AttributeError: 'NoneType' object has no attribute 'ping'
Steps to reproduce
- Set a very short websocket heartbeat interval.
- Insert a random async delay (which is larger than the heartbeat interval) into
StreamResponse.prepareto simulate slow client. - Connect a websocket client to the server.
Your environment
- aiohttp 3.6.0
- uvloop 0.13.0
- python 3.7.1 on linux
Metadata
Assignees
Labels
No labels