diff --git a/tornado/websocket.py b/tornado/websocket.py index fbfd700887..fc7bf1b30f 100644 --- a/tornado/websocket.py +++ b/tornado/websocket.py @@ -11,6 +11,8 @@ Removed support for the draft 76 protocol version. """ +from time import time + import abc import asyncio import base64 @@ -835,7 +837,6 @@ def __init__( self._wire_bytes_in = 0 self._wire_bytes_out = 0 self.ping_callback = None # type: Optional[PeriodicCallback] - self.last_ping = 0.0 self.last_pong = 0.0 self.close_code = None # type: Optional[int] self.close_reason = None # type: Optional[str] @@ -1230,6 +1231,7 @@ def _handle_message(self, opcode: int, data: bytes) -> "Optional[Future[None]]": self.close(self.close_code) elif opcode == 0x9: # Ping + print(f':ping: {time()}') try: self._write_frame(True, 0xA, data) except StreamClosedError: @@ -1237,6 +1239,7 @@ def _handle_message(self, opcode: int, data: bytes) -> "Optional[Future[None]]": self._run_callback(self.handler.on_ping, data) elif opcode == 0xA: # Pong + print(f':pong: {time()}') self.last_pong = IOLoop.current().time() return self._run_callback(self.handler.on_pong, data) else: @@ -1303,38 +1306,34 @@ def start_pinging(self) -> None: """Start sending periodic pings to keep the connection alive""" assert self.ping_interval is not None if self.ping_interval > 0: - self.last_ping = self.last_pong = IOLoop.current().time() + if self.ping_timeout and self.ping_timeout > self.ping_interval: + raise ValueError( + 'the ping_timeout cannot be greater than the ping_interval' + ) self.ping_callback = PeriodicCallback( self.periodic_ping, self.ping_interval * 1000 ) self.ping_callback.start() - def periodic_ping(self) -> None: - """Send a ping to keep the websocket alive + async def periodic_ping(self) -> None: + """Send a ping and wait for a pong if ping_timeout is configured. Called periodically if the websocket_ping_interval is set and non-zero. """ - if self.is_closing() and self.ping_callback is not None: - self.ping_callback.stop() - return - - # Check for timeout on pong. Make sure that we really have - # sent a recent ping in case the machine with both server and - # client has been suspended since the last ping. now = IOLoop.current().time() - since_last_pong = now - self.last_pong - since_last_ping = now - self.last_ping - assert self.ping_interval is not None - assert self.ping_timeout is not None - if ( - since_last_ping < 2 * self.ping_interval - and since_last_pong > self.ping_timeout - ): - self.close() - return + # send a ping + print(f':ping: {time()}') self.write_ping(b"") - self.last_ping = now + + if self.ping_timeout and self.ping_timeout > 0: + # wait for the pong + await asyncio.sleep(self.ping_timeout) + + # close the connection if the pong is not received within the + # configured timeout + if self.last_pong - now > self.ping_timeout: + self.close() def set_nodelay(self, x: bool) -> None: self.stream.set_nodelay(x)