Skip to content

Commit 866acfc

Browse files
websockets: fix ping_timeout
* Closes #3258 * Fixes an issue with the calculation of ping timeout interval that could cause connections to be erroneously timed out and closed from the server end.
1 parent f399f40 commit 866acfc

File tree

2 files changed

+18
-24
lines changed

2 files changed

+18
-24
lines changed

Diff for: tornado/test/websocket_test.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -806,7 +806,11 @@ class PingHandler(TestWebSocketHandler):
806806
def on_pong(self, data):
807807
self.write_message("got pong")
808808

809-
return Application([("/", PingHandler)], websocket_ping_interval=0.01)
809+
return Application(
810+
[("/", PingHandler)],
811+
websocket_ping_interval=0.01,
812+
websocket_ping_timeout=1,
813+
)
810814

811815
@gen_test
812816
def test_server_ping(self):
@@ -827,7 +831,7 @@ def on_ping(self, data):
827831

828832
@gen_test
829833
def test_client_ping(self):
830-
ws = yield self.ws_connect("/", ping_interval=0.01)
834+
ws = yield self.ws_connect("/", ping_interval=0.01, ping_timeout=1)
831835
for i in range(3):
832836
response = yield ws.read_message()
833837
self.assertEqual(response, "got ping")

Diff for: tornado/websocket.py

+12-22
Original file line numberDiff line numberDiff line change
@@ -835,7 +835,6 @@ def __init__(
835835
self._wire_bytes_in = 0
836836
self._wire_bytes_out = 0
837837
self.ping_callback = None # type: Optional[PeriodicCallback]
838-
self.last_ping = 0.0
839838
self.last_pong = 0.0
840839
self.close_code = None # type: Optional[int]
841840
self.close_reason = None # type: Optional[str]
@@ -1303,38 +1302,29 @@ def start_pinging(self) -> None:
13031302
"""Start sending periodic pings to keep the connection alive"""
13041303
assert self.ping_interval is not None
13051304
if self.ping_interval > 0:
1306-
self.last_ping = self.last_pong = IOLoop.current().time()
13071305
self.ping_callback = PeriodicCallback(
13081306
self.periodic_ping, self.ping_interval * 1000
13091307
)
13101308
self.ping_callback.start()
13111309

1312-
def periodic_ping(self) -> None:
1313-
"""Send a ping to keep the websocket alive
1310+
async def periodic_ping(self) -> None:
1311+
"""Send a ping and wait for a pong if ping_timeout is configured.
13141312
13151313
Called periodically if the websocket_ping_interval is set and non-zero.
13161314
"""
1317-
if self.is_closing() and self.ping_callback is not None:
1318-
self.ping_callback.stop()
1319-
return
1320-
1321-
# Check for timeout on pong. Make sure that we really have
1322-
# sent a recent ping in case the machine with both server and
1323-
# client has been suspended since the last ping.
13241315
now = IOLoop.current().time()
1325-
since_last_pong = now - self.last_pong
1326-
since_last_ping = now - self.last_ping
1327-
assert self.ping_interval is not None
1328-
assert self.ping_timeout is not None
1329-
if (
1330-
since_last_ping < 2 * self.ping_interval
1331-
and since_last_pong > self.ping_timeout
1332-
):
1333-
self.close()
1334-
return
13351316

1317+
# send a ping
13361318
self.write_ping(b"")
1337-
self.last_ping = now
1319+
1320+
if self.ping_timeout and self.ping_timeout > 0:
1321+
# wait for the pong
1322+
await asyncio.sleep(self.ping_timeout)
1323+
1324+
# close the connection if the pong is not received within the
1325+
# configured timeout
1326+
if self.last_pong - now > self.ping_timeout:
1327+
self.close()
13381328

13391329
def set_nodelay(self, x: bool) -> None:
13401330
self.stream.set_nodelay(x)

0 commit comments

Comments
 (0)