Skip to content

Commit eaddd9c

Browse files
[PR #8251/c21b76d0 backport][3.9] Leave websocket transport open if receive times out or is cancelled (#8263)
Co-authored-by: J. Nick Koston <[email protected]>
1 parent 5c248fc commit eaddd9c

File tree

4 files changed

+100
-3
lines changed

4 files changed

+100
-3
lines changed

CHANGES/8251.bugfix.rst

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Leave websocket transport open if receive times out or is cancelled
2+
-- by :user:`bdraco`.
3+
4+
This restores the behavior prior to the change in #7978.

aiohttp/web_ws.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -462,8 +462,7 @@ async def receive(self, timeout: Optional[float] = None) -> WSMessage:
462462
waiter = self._waiting
463463
set_result(waiter, True)
464464
self._waiting = None
465-
except (asyncio.CancelledError, asyncio.TimeoutError):
466-
self._set_code_close_transport(WSCloseCode.ABNORMAL_CLOSURE)
465+
except asyncio.TimeoutError:
467466
raise
468467
except EofStream:
469468
self._close_code = WSCloseCode.OK

tests/test_web_websocket.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,8 @@ async def test_receive_timeouterror(make_request: Any, loop: Any) -> None:
472472
with pytest.raises(asyncio.TimeoutError):
473473
await ws.receive()
474474

475-
assert len(ws._req.transport.close.mock_calls) == 1
475+
# Should not close the connection on timeout
476+
assert len(ws._req.transport.close.mock_calls) == 0
476477

477478

478479
async def test_multiple_receive_on_close_connection(make_request) -> None:

tests/test_web_websocket_functional.py

+93
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# HTTP websocket server functional tests
22

33
import asyncio
4+
import contextlib
5+
import sys
46
from typing import Any, Optional
57

68
import pytest
@@ -797,3 +799,94 @@ async def ws_handler(request):
797799
resp = await client.get("/api/null", timeout=1)
798800
assert (await resp.json()) == {"err": None}
799801
resp.close()
802+
803+
804+
async def test_receive_being_cancelled_keeps_connection_open(
805+
loop: Any, aiohttp_client: Any
806+
) -> None:
807+
closed = loop.create_future()
808+
809+
async def handler(request):
810+
ws = web.WebSocketResponse(autoping=False)
811+
await ws.prepare(request)
812+
813+
task = asyncio.create_task(ws.receive())
814+
await asyncio.sleep(0)
815+
task.cancel()
816+
with contextlib.suppress(asyncio.CancelledError):
817+
await task
818+
819+
msg = await ws.receive()
820+
assert msg.type == WSMsgType.PING
821+
await asyncio.sleep(0)
822+
await ws.pong("data")
823+
824+
msg = await ws.receive()
825+
assert msg.type == WSMsgType.CLOSE
826+
assert msg.data == WSCloseCode.OK
827+
assert msg.extra == "exit message"
828+
closed.set_result(None)
829+
return ws
830+
831+
app = web.Application()
832+
app.router.add_get("/", handler)
833+
client = await aiohttp_client(app)
834+
835+
ws = await client.ws_connect("/", autoping=False)
836+
837+
await asyncio.sleep(0)
838+
await ws.ping("data")
839+
840+
msg = await ws.receive()
841+
assert msg.type == WSMsgType.PONG
842+
assert msg.data == b"data"
843+
844+
await ws.close(code=WSCloseCode.OK, message="exit message")
845+
846+
await closed
847+
848+
849+
async def test_receive_timeout_keeps_connection_open(
850+
loop: Any, aiohttp_client: Any
851+
) -> None:
852+
closed = loop.create_future()
853+
timed_out = loop.create_future()
854+
855+
async def handler(request):
856+
ws = web.WebSocketResponse(autoping=False)
857+
await ws.prepare(request)
858+
859+
task = asyncio.create_task(ws.receive(sys.float_info.min))
860+
with contextlib.suppress(asyncio.TimeoutError):
861+
await task
862+
863+
timed_out.set_result(None)
864+
865+
msg = await ws.receive()
866+
assert msg.type == WSMsgType.PING
867+
await asyncio.sleep(0)
868+
await ws.pong("data")
869+
870+
msg = await ws.receive()
871+
assert msg.type == WSMsgType.CLOSE
872+
assert msg.data == WSCloseCode.OK
873+
assert msg.extra == "exit message"
874+
closed.set_result(None)
875+
return ws
876+
877+
app = web.Application()
878+
app.router.add_get("/", handler)
879+
client = await aiohttp_client(app)
880+
881+
ws = await client.ws_connect("/", autoping=False)
882+
883+
await timed_out
884+
await ws.ping("data")
885+
886+
msg = await ws.receive()
887+
assert msg.type == WSMsgType.PONG
888+
assert msg.data == b"data"
889+
890+
await ws.close(code=WSCloseCode.OK, message="exit message")
891+
892+
await closed

0 commit comments

Comments
 (0)