Skip to content

Commit 9d355bf

Browse files
committed
Remove unnecessary code paths in keepalive().
Also add comments in tests to clarify the intended sequence.
1 parent 453e55a commit 9d355bf

File tree

2 files changed

+42
-22
lines changed

2 files changed

+42
-22
lines changed

src/websockets/asyncio/connection.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -723,6 +723,10 @@ async def keepalive(self) -> None:
723723
if self.ping_timeout is not None:
724724
try:
725725
async with asyncio_timeout(self.ping_timeout):
726+
# connection_lost cancels keepalive immediately
727+
# after setting a ConnectionClosed exception on
728+
# pong_waiter. A CancelledError is raised here,
729+
# not a ConnectionClosed exception.
726730
latency = await pong_waiter
727731
self.logger.debug("% received keepalive pong")
728732
except asyncio.TimeoutError:
@@ -733,9 +737,10 @@ async def keepalive(self) -> None:
733737
CloseCode.INTERNAL_ERROR,
734738
"keepalive ping timeout",
735739
)
736-
break
737-
except ConnectionClosed:
738-
pass
740+
raise AssertionError(
741+
"send_context() should wait for connection_lost(), "
742+
"which cancels keepalive()"
743+
)
739744
except Exception:
740745
self.logger.error("keepalive ping failed", exc_info=True)
741746

@@ -913,8 +918,7 @@ def connection_lost(self, exc: Exception | None) -> None:
913918
self.set_recv_exc(exc)
914919
self.recv_messages.close()
915920
self.abort_pings()
916-
# If keepalive() was waiting for a pong, abort_pings() terminated it.
917-
# If it was sleeping until the next ping, we need to cancel it now
921+
918922
if self.keepalive_task is not None:
919923
self.keepalive_task.cancel()
920924

tests/asyncio/test_connection.py

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -890,12 +890,25 @@ async def test_pong_explicit_binary(self):
890890

891891
@patch("random.getrandbits")
892892
async def test_keepalive(self, getrandbits):
893-
"""keepalive sends pings."""
893+
"""keepalive sends pings at ping_interval and measures latency."""
894894
self.connection.ping_interval = 2 * MS
895895
getrandbits.return_value = 1918987876
896896
self.connection.start_keepalive()
897+
self.assertEqual(self.connection.latency, 0)
898+
# 2 ms: keepalive() sends a ping frame.
899+
# 2.x ms: a pong frame is received.
897900
await asyncio.sleep(3 * MS)
901+
# 3 ms: check that the ping frame was sent.
898902
await self.assertFrameSent(Frame(Opcode.PING, b"rand"))
903+
self.assertGreater(self.connection.latency, 0)
904+
self.assertLess(self.connection.latency, MS)
905+
906+
async def test_disable_keepalive(self):
907+
"""keepalive is disabled when ping_interval is None."""
908+
self.connection.ping_interval = None
909+
self.connection.start_keepalive()
910+
await asyncio.sleep(3 * MS)
911+
await self.assertNoFrameSent()
899912

900913
@patch("random.getrandbits")
901914
async def test_keepalive_times_out(self, getrandbits):
@@ -905,13 +918,14 @@ async def test_keepalive_times_out(self, getrandbits):
905918
async with self.drop_frames_rcvd():
906919
getrandbits.return_value = 1918987876
907920
self.connection.start_keepalive()
921+
# 4 ms: keepalive() sends a ping frame.
908922
await asyncio.sleep(4 * MS)
909923
# Exiting the context manager sleeps for MS.
910-
await self.assertFrameSent(Frame(Opcode.PING, b"rand"))
911-
await asyncio.sleep(MS)
912-
await self.assertFrameSent(
913-
Frame(Opcode.CLOSE, b"\x03\xf3keepalive ping timeout")
914-
)
924+
# 4.x ms: a pong frame is dropped.
925+
# 6 ms: no pong frame is received; the connection is closed.
926+
await asyncio.sleep(2 * MS)
927+
# 7 ms: check that the connection is closed.
928+
self.assertEqual(self.connection.state, State.CLOSED)
915929

916930
@patch("random.getrandbits")
917931
async def test_keepalive_ignores_timeout(self, getrandbits):
@@ -921,18 +935,14 @@ async def test_keepalive_ignores_timeout(self, getrandbits):
921935
async with self.drop_frames_rcvd():
922936
getrandbits.return_value = 1918987876
923937
self.connection.start_keepalive()
938+
# 4 ms: keepalive() sends a ping frame.
924939
await asyncio.sleep(4 * MS)
925940
# Exiting the context manager sleeps for MS.
926-
await self.assertFrameSent(Frame(Opcode.PING, b"rand"))
927-
await asyncio.sleep(MS)
928-
await self.assertNoFrameSent()
929-
930-
async def test_disable_keepalive(self):
931-
"""keepalive is disabled when ping_interval is None."""
932-
self.connection.ping_interval = None
933-
self.connection.start_keepalive()
934-
await asyncio.sleep(3 * MS)
935-
await self.assertNoFrameSent()
941+
# 4.x ms: a pong frame is dropped.
942+
# 6 ms: no pong frame is received; the connection remains open.
943+
await asyncio.sleep(2 * MS)
944+
# 7 ms: check that the connection is still open.
945+
self.assertEqual(self.connection.state, State.OPEN)
936946

937947
async def test_keepalive_terminates_while_sleeping(self):
938948
"""keepalive task terminates while waiting to send a ping."""
@@ -945,21 +955,27 @@ async def test_keepalive_terminates_while_sleeping(self):
945955
async def test_keepalive_terminates_while_waiting_for_pong(self):
946956
"""keepalive task terminates while waiting to receive a pong."""
947957
self.connection.ping_interval = 2 * MS
958+
self.connection.ping_timeout = 2 * MS
948959
async with self.drop_frames_rcvd():
949960
self.connection.start_keepalive()
961+
# 2 ms: keepalive() sends a ping frame.
950962
await asyncio.sleep(2 * MS)
951963
# Exiting the context manager sleeps for MS.
964+
# 2.x ms: a pong frame is dropped.
965+
# 3 ms: close the connection before ping_timeout elapses.
952966
await self.connection.close()
953967
self.assertTrue(self.connection.keepalive_task.done())
954968

955969
async def test_keepalive_reports_errors(self):
956970
"""keepalive reports unexpected errors in logs."""
957971
self.connection.ping_interval = 2 * MS
958-
# Inject a fault by raising an exception in a pending pong waiter.
959972
async with self.drop_frames_rcvd():
960973
self.connection.start_keepalive()
974+
# 2 ms: keepalive() sends a ping frame.
961975
await asyncio.sleep(2 * MS)
962976
# Exiting the context manager sleeps for MS.
977+
# 2.x ms: a pong frame is dropped.
978+
# 3 ms: inject a fault: raise an exception in the pending pong waiter.
963979
pong_waiter = next(iter(self.connection.pong_waiters.values()))[0]
964980
with self.assertLogs("websockets", logging.ERROR) as logs:
965981
pong_waiter.set_exception(Exception("BOOM"))

0 commit comments

Comments
 (0)