@@ -100,19 +100,19 @@ def __init__(
100100 # Deadline for the closing handshake.
101101 self .close_deadline : float | None = None
102102
103- # Protect sending fragmented messages .
103+ # Whether we are busy sending a fragmented message .
104104 self .send_in_progress : asyncio .Future [None ] | None = None
105105
106106 # Mapping of ping IDs to pong waiters, in chronological order.
107107 self .pending_pings : dict [bytes , tuple [asyncio .Future [float ], float ]] = {}
108108
109- self .latency : float = 0
109+ self .latency : float = 0.0
110110 """
111111 Latency of the connection, in seconds.
112112
113113 Latency is defined as the round-trip time of the connection. It is
114114 measured by sending a Ping frame and waiting for a matching Pong frame.
115- Before the first measurement, :attr:`latency` is ``0``.
115+ Before the first measurement, :attr:`latency` is ``0.0 ``.
116116
117117 By default, websockets enables a :ref:`keepalive <keepalive>` mechanism
118118 that sends Ping frames automatically at regular intervals. You can also
@@ -130,7 +130,7 @@ def __init__(
130130 # connection state becomes CLOSED.
131131 self .connection_lost_waiter : asyncio .Future [None ] = self .loop .create_future ()
132132
133- # Adapted from asyncio.FlowControlMixin
133+ # Adapted from asyncio.FlowControlMixin.
134134 self .paused : bool = False
135135 self .drain_waiters : collections .deque [asyncio .Future [None ]] = (
136136 collections .deque ()
@@ -291,9 +291,9 @@ async def recv(self, decode: bool | None = None) -> Data:
291291 return a bytestring (:class:`bytes`). This improves performance
292292 when decoding isn't needed, for example if the message contains
293293 JSON and you're using a JSON library that expects a bytestring.
294- * Set ``decode=True`` to force UTF-8 decoding of Binary_ frames
295- and return a string (:class:`str`). This may be useful for
296- servers that send binary frames instead of text frames.
294+ * Set ``decode=True`` to force UTF-8 decoding of Binary_ frames and
295+ return strings (:class:`str`). This may be useful for servers that
296+ send binary frames instead of text frames.
297297
298298 Raises:
299299 ConnectionClosed: When the connection is closed.
@@ -363,12 +363,12 @@ async def recv_streaming(self, decode: bool | None = None) -> AsyncIterator[Data
363363
364364 You may override this behavior with the ``decode`` argument:
365365
366- * Set ``decode=False`` to disable UTF-8 decoding of Text_ frames
367- and return bytestrings (:class:`bytes`). This may be useful to
368- optimize performance when decoding isn't needed.
369- * Set ``decode=True`` to force UTF-8 decoding of Binary_ frames
370- and return strings (:class:`str`). This is useful for servers
371- that send binary frames instead of text frames.
366+ * Set ``decode=False`` to disable UTF-8 decoding of Text_ frames and
367+ yield bytestrings (:class:`bytes`). This improves performance
368+ when decoding isn't needed.
369+ * Set ``decode=True`` to force UTF-8 decoding of Binary_ frames and
370+ yield strings (:class:`str`). This may be useful for servers that
371+ send binary frames instead of text frames.
372372
373373 Raises:
374374 ConnectionClosed: When the connection is closed.
@@ -417,16 +417,16 @@ async def send(
417417
418418 You may override this behavior with the ``text`` argument:
419419
420- * Set ``text=True`` to send a bytestring or bytes-like object
421- (:class:`bytes`, :class:`bytearray`, or :class:`memoryview`) as a
420+ * Set ``text=True`` to send an UTF-8 bytestring or bytes-like object
421+ (:class:`bytes`, :class:`bytearray`, or :class:`memoryview`) in a
422422 Text_ frame. This improves performance when the message is already
423423 UTF-8 encoded, for example if the message contains JSON and you're
424424 using a JSON library that produces a bytestring.
425425 * Set ``text=False`` to send a string (:class:`str`) in a Binary_
426426 frame. This may be useful for servers that expect binary frames
427427 instead of text frames.
428428
429- :meth:`send` also accepts an iterable or an asynchronous iterable of
429+ :meth:`send` also accepts an iterable or asynchronous iterable of
430430 strings, bytestrings, or bytes-like objects to enable fragmentation_.
431431 Each item is treated as a message fragment and sent in its own frame.
432432 All items must be of the same type, or else :meth:`send` will raise a
@@ -441,8 +441,8 @@ async def send(
441441 Canceling :meth:`send` is discouraged. Instead, you should close the
442442 connection with :meth:`close`. Indeed, there are only two situations
443443 where :meth:`send` may yield control to the event loop and then get
444- canceled; in both cases, :meth:`close` has the same effect and is
445- more clear :
444+ canceled; in both cases, :meth:`close` has the same effect and the
445+ effect is more obvious :
446446
447447 1. The write buffer is full. If you don't want to wait until enough
448448 data is sent, your only alternative is to close the connection.
@@ -708,9 +708,10 @@ async def ping(self, data: DataLike | None = None) -> Awaitable[float]:
708708 data = struct .pack ("!I" , random .getrandbits (32 ))
709709
710710 pong_received = self .loop .create_future ()
711+ ping_timestamp = self .loop .time ()
711712 # The event loop's default clock is time.monotonic(). Its resolution
712713 # is a bit low on Windows (~16ms). This is improved in Python 3.13.
713- self .pending_pings [data ] = (pong_received , self . loop . time () )
714+ self .pending_pings [data ] = (pong_received , ping_timestamp )
714715 self .protocol .send_ping (data )
715716 return pong_received
716717
@@ -787,9 +788,7 @@ def acknowledge_pings(self, data: bytes) -> None:
787788
788789 def terminate_pending_pings (self ) -> None :
789790 """
790- Raise ConnectionClosed in pending pings.
791-
792- They'll never receive a pong once the connection is closed.
791+ Raise ConnectionClosed in pending pings when the connection is closed.
793792
794793 """
795794 assert self .protocol .state is CLOSED
@@ -837,7 +836,8 @@ async def keepalive(self) -> None:
837836 # pong_received. A CancelledError is raised here,
838837 # not a ConnectionClosed exception.
839838 latency = await pong_received
840- self .logger .debug ("% received keepalive pong" )
839+ if self .debug :
840+ self .logger .debug ("% received keepalive pong" )
841841 except asyncio .TimeoutError :
842842 if self .debug :
843843 self .logger .debug ("- timed out waiting for keepalive pong" )
@@ -908,20 +908,22 @@ async def send_context(
908908 # Check if the connection is expected to close soon.
909909 if self .protocol .close_expected ():
910910 wait_for_close = True
911- # If the connection is expected to close soon, set the
912- # close deadline based on the close timeout.
913- # Since we tested earlier that protocol.state was OPEN
911+ # Set the close deadline based on the close timeout.
912+ # Since we tested earlier that protocol.state is OPEN
914913 # (or CONNECTING), self.close_deadline is still None.
914+ assert self .close_deadline is None
915915 if self .close_timeout is not None :
916- assert self .close_deadline is None
917916 self .close_deadline = self .loop .time () + self .close_timeout
918- # Write outgoing data to the socket and enforce flow control.
917+ # Write outgoing data to the socket with flow control.
919918 try :
920919 self .send_data ()
921920 await self .drain ()
922921 except Exception as exc :
923922 if self .debug :
924- self .logger .debug ("! error while sending data" , exc_info = True )
923+ self .logger .debug (
924+ "! error while sending data" ,
925+ exc_info = True ,
926+ )
925927 # While the only expected exception here is OSError,
926928 # other exceptions would be treated identically.
927929 wait_for_close = False
@@ -933,8 +935,8 @@ async def send_context(
933935 # will be closing soon if it isn't in the expected state.
934936 wait_for_close = True
935937 # Calculate close_deadline if it wasn't set yet.
936- if self .close_timeout is not None :
937- if self .close_deadline is None :
938+ if self .close_deadline is None :
939+ if self .close_timeout is not None :
938940 self .close_deadline = self .loop .time () + self .close_timeout
939941 raise_close_exc = True
940942
@@ -945,7 +947,7 @@ async def send_context(
945947 async with asyncio_timeout_at (self .close_deadline ):
946948 await asyncio .shield (self .connection_lost_waiter )
947949 except TimeoutError :
948- # There's no risk to overwrite another error because
950+ # There's no risk of overwriting another error because
949951 # original_exc is never set when wait_for_close is True.
950952 assert original_exc is None
951953 original_exc = TimeoutError ("timed out while closing connection" )
@@ -966,9 +968,6 @@ def send_data(self) -> None:
966968 """
967969 Send outgoing data.
968970
969- Raises:
970- OSError: When a socket operations fails.
971-
972971 """
973972 for data in self .protocol .data_to_send ():
974973 if data :
@@ -982,7 +981,7 @@ def send_data(self) -> None:
982981 # OSError is plausible. uvloop can raise RuntimeError here.
983982 try :
984983 self .transport .write_eof ()
985- except ( OSError , RuntimeError ) : # pragma: no cover
984+ except Exception : # pragma: no cover
986985 pass
987986 # Else, close the TCP connection.
988987 else : # pragma: no cover
@@ -994,6 +993,8 @@ def set_recv_exc(self, exc: BaseException | None) -> None:
994993 """
995994 Set recv_exc, if not set yet.
996995
996+ This method must be called only from connection callbacks.
997+
997998 """
998999 if self .recv_exc is None :
9991000 self .recv_exc = exc
@@ -1096,26 +1097,29 @@ def data_received(self, data: bytes) -> None:
10961097 self .logger .debug ("! error while sending data" , exc_info = True )
10971098 self .set_recv_exc (exc )
10981099
1100+ # If needed, set the close deadline based on the close timeout.
10991101 if self .protocol .close_expected ():
1100- # If the connection is expected to close soon, set the
1101- # close deadline based on the close timeout.
1102- if self .close_timeout is not None :
1103- if self .close_deadline is None :
1102+ if self .close_deadline is None :
1103+ if self .close_timeout is not None :
11041104 self .close_deadline = self .loop .time () + self .close_timeout
11051105
1106+ # If self.send_data raised an exception, then events are lost.
1107+ # Given that automatic responses write small amounts of data,
1108+ # this should be uncommon, so we don't handle the edge case.
1109+
11061110 for event in events :
11071111 # This isn't expected to raise an exception.
11081112 self .process_event (event )
11091113
11101114 def eof_received (self ) -> None :
1111- # Feed the end of the data stream to the connection .
1115+ # Feed the end of the data stream to the protocol .
11121116 self .protocol .receive_eof ()
11131117
11141118 # This isn't expected to raise an exception.
11151119 events = self .protocol .events_received ()
11161120
11171121 # There is no error handling because send_data() can only write
1118- # the end of the data stream here and it shouldn't raise errors.
1122+ # the end of the data stream and it handles errors by itself .
11191123 self .send_data ()
11201124
11211125 # This code path is triggered when receiving an HTTP response
0 commit comments