Skip to content

Commit c21b76d

Browse files
authored
Leave websocket transport open if receive times out or is cancelled (#8251)
1 parent 779505c commit c21b76d

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
@@ -488,8 +488,7 @@ async def receive(self, timeout: Optional[float] = None) -> WSMessage:
488488
waiter = self._waiting
489489
set_result(waiter, True)
490490
self._waiting = None
491-
except (asyncio.CancelledError, asyncio.TimeoutError):
492-
self._set_code_close_transport(WSCloseCode.ABNORMAL_CLOSURE)
491+
except asyncio.TimeoutError:
493492
raise
494493
except EofStream:
495494
self._close_code = WSCloseCode.OK

tests/test_web_websocket.py

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

448-
assert len(ws._req.transport.close.mock_calls) == 1
448+
# Should not close the connection on timeout
449+
assert len(ws._req.transport.close.mock_calls) == 0
449450

450451

451452
async def test_multiple_receive_on_close_connection(make_request: Any) -> None:

tests/test_web_websocket_functional.py

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

44
import asyncio
5+
import contextlib
6+
import sys
57
from typing import Any, Optional
68

79
import pytest
@@ -828,3 +830,94 @@ async def ws_handler(request):
828830
resp = await client.get("/api/null", timeout=aiohttp.ClientTimeout(total=1))
829831
assert (await resp.json()) == {"err": None}
830832
resp.close()
833+
834+
835+
async def test_receive_being_cancelled_keeps_connection_open(
836+
loop: Any, aiohttp_client: Any
837+
) -> None:
838+
closed = loop.create_future()
839+
840+
async def handler(request):
841+
ws = web.WebSocketResponse(autoping=False)
842+
await ws.prepare(request)
843+
844+
task = asyncio.create_task(ws.receive())
845+
await asyncio.sleep(0)
846+
task.cancel()
847+
with contextlib.suppress(asyncio.CancelledError):
848+
await task
849+
850+
msg = await ws.receive()
851+
assert msg.type == WSMsgType.PING
852+
await asyncio.sleep(0)
853+
await ws.pong("data")
854+
855+
msg = await ws.receive()
856+
assert msg.type == WSMsgType.CLOSE
857+
assert msg.data == WSCloseCode.OK
858+
assert msg.extra == "exit message"
859+
closed.set_result(None)
860+
return ws
861+
862+
app = web.Application()
863+
app.router.add_get("/", handler)
864+
client = await aiohttp_client(app)
865+
866+
ws = await client.ws_connect("/", autoping=False)
867+
868+
await asyncio.sleep(0)
869+
await ws.ping("data")
870+
871+
msg = await ws.receive()
872+
assert msg.type == WSMsgType.PONG
873+
assert msg.data == b"data"
874+
875+
await ws.close(code=WSCloseCode.OK, message="exit message")
876+
877+
await closed
878+
879+
880+
async def test_receive_timeout_keeps_connection_open(
881+
loop: Any, aiohttp_client: Any
882+
) -> None:
883+
closed = loop.create_future()
884+
timed_out = loop.create_future()
885+
886+
async def handler(request):
887+
ws = web.WebSocketResponse(autoping=False)
888+
await ws.prepare(request)
889+
890+
task = asyncio.create_task(ws.receive(sys.float_info.min))
891+
with contextlib.suppress(asyncio.TimeoutError):
892+
await task
893+
894+
timed_out.set_result(None)
895+
896+
msg = await ws.receive()
897+
assert msg.type == WSMsgType.PING
898+
await asyncio.sleep(0)
899+
await ws.pong("data")
900+
901+
msg = await ws.receive()
902+
assert msg.type == WSMsgType.CLOSE
903+
assert msg.data == WSCloseCode.OK
904+
assert msg.extra == "exit message"
905+
closed.set_result(None)
906+
return ws
907+
908+
app = web.Application()
909+
app.router.add_get("/", handler)
910+
client = await aiohttp_client(app)
911+
912+
ws = await client.ws_connect("/", autoping=False)
913+
914+
await timed_out
915+
await ws.ping("data")
916+
917+
msg = await ws.receive()
918+
assert msg.type == WSMsgType.PONG
919+
assert msg.data == b"data"
920+
921+
await ws.close(code=WSCloseCode.OK, message="exit message")
922+
923+
await closed

0 commit comments

Comments
 (0)