Skip to content

Commit 97e4611

Browse files
committed
wip
1 parent 08e04d6 commit 97e4611

File tree

4 files changed

+54
-21
lines changed

4 files changed

+54
-21
lines changed

docs/reference/index.rst

+11
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,17 @@ clients concurrently.
2626
asyncio/server
2727
asyncio/client
2828

29+
:mod:`asyncio` (new)
30+
--------------------
31+
32+
This is a rewrite of the :mod:`asyncio` implementation.
33+
34+
.. toctree::
35+
:titlesonly:
36+
37+
new-asyncio/server
38+
new-asyncio/client
39+
2940
:mod:`threading`
3041
----------------
3142

src/websockets/asyncio/connection.py

+41-17
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,28 @@
88
from types import TracebackType
99
from typing import (
1010
Any,
11+
AsyncIterable,
12+
AsyncIterator,
1113
Dict,
1214
Iterable,
1315
Iterator,
1416
Mapping,
1517
Optional,
18+
Tuple,
1619
Type,
17-
Union,
1820
)
1921

2022
from ..asyncio.deadline import Deadline
2123
from ..exceptions import ConnectionClosed, ConnectionClosedOK
22-
from ..frames import DATA_OPCODES, BytesLike, Frame, Opcode, prepare_ctrl
24+
from ..frames import DATA_OPCODES, BytesLike, CloseCode, Frame, Opcode, prepare_ctrl
2325
from ..http11 import Request, Response
2426
from ..protocol import Event, Protocol
25-
from ..typing import AsyncIterator, Data, LoggerLike, Subprotocol
27+
from ..typing import Data, LoggerLike, Subprotocol
2628
from .messages import Assembler
2729

2830

2931
__all__ = ["Connection"]
3032

31-
logger = logging.getLogger(__name__)
32-
3333

3434
class Connection(asyncio.Protocol):
3535
"""
@@ -75,14 +75,14 @@ def __init__(
7575
# Assembler turning frames into messages and serializing reads.
7676
self.recv_messages = Assembler()
7777

78-
# Whether we are busy sending a fragmented message.
79-
self.send_in_progress = False
80-
8178
# Deadline for the closing handshake.
8279
self.close_deadline: Optional[Deadline] = None
8380

81+
# Protect sending fragmented messages.
82+
self.fragmented_send_waiter: Optional[asyncio.Future[None]] = None
83+
8484
# Mapping of ping IDs to pong waiters, in chronological order.
85-
self.pings: Dict[bytes, asyncio.Future] = {}
85+
self.pings: Dict[bytes, Tuple[asyncio.Future[float], float]] = {}
8686

8787
# Exception raised in recv_events, to be chained to ConnectionClosed
8888
# in the user thread in order to show why the TCP connection dropped.
@@ -137,13 +137,17 @@ async def __aexit__(
137137
exc_value: Optional[BaseException],
138138
traceback: Optional[TracebackType],
139139
) -> None:
140-
await self.close(1000 if exc_type is None else 1011)
140+
if exc_type is None:
141+
await self.close()
142+
else:
143+
await self.close(CloseCode.INTERNAL_ERROR)
141144

142145
async def __aiter__(self) -> Iterator[Data]:
143146
"""
144147
Iterate on incoming messages.
145148
146-
The iterator calls :meth:`recv` and yields messages in an infinite loop.
149+
The iterator calls :meth:`recv` and yields messages asynchronously in an
150+
infinite loop.
147151
148152
It exits when the connection is closed normally. It raises a
149153
:exc:`~websockets.exceptions.ConnectionClosedError` exception after a
@@ -203,6 +207,8 @@ async def recv_streaming(self) -> AsyncIterator[Data]:
203207
"""
204208
Receive the next message frame by frame.
205209
210+
Canceling :meth:`send` is discouraged. TODO is it recoverable?
211+
206212
If the message is fragmented, yield each fragment as it is received.
207213
The iterator must be fully consumed, or else the connection will become
208214
unusable.
@@ -233,7 +239,7 @@ async def recv_streaming(self) -> AsyncIterator[Data]:
233239
"is already running recv or recv_streaming"
234240
) from None
235241

236-
async def send(self, message: Union[Data, Iterable[Data]]) -> None:
242+
async def send(self, message: Data | Iterable[Data] | AsyncIterable[Data]) -> None:
237243
"""
238244
Send a message.
239245
@@ -244,18 +250,31 @@ async def send(self, message: Union[Data, Iterable[Data]]) -> None:
244250
.. _Text: https://www.rfc-editor.org/rfc/rfc6455.html#section-5.6
245251
.. _Binary: https://www.rfc-editor.org/rfc/rfc6455.html#section-5.6
246252
247-
:meth:`send` also accepts an iterable of strings, bytestrings, or
248-
bytes-like objects to enable fragmentation_. Each item is treated as a
249-
message fragment and sent in its own frame. All items must be of the
250-
same type, or else :meth:`send` will raise a :exc:`TypeError` and the
251-
connection will be closed.
253+
:meth:`send` also accepts an iterable or an asynchronous iterable of
254+
strings, bytestrings, or bytes-like objects to enable fragmentation_.
255+
Each item is treated as a message fragment and sent in its own frame.
256+
All items must be of the same type, or else :meth:`send` will raise a
257+
:exc:`TypeError` and the connection will be closed.
252258
253259
.. _fragmentation: https://www.rfc-editor.org/rfc/rfc6455.html#section-5.4
254260
255261
:meth:`send` rejects dict-like objects because this is often an error.
256262
(If you really want to send the keys of a dict-like object as fragments,
257263
call its :meth:`~dict.keys` method and pass the result to :meth:`send`.)
258264
265+
Canceling :meth:`send` is discouraged. Instead, you should close the
266+
connection with :meth:`close`. Indeed, there are only two situations
267+
where :meth:`send` may yield control to the event loop and then get
268+
canceled; in both cases, :meth:`close` has the same effect and is
269+
more clear:
270+
271+
1. The write buffer is full. If you don't want to wait until enough
272+
data is sent, your only alternative is to close the connection.
273+
:meth:`close` will likely time out then abort the TCP connection.
274+
2. ``message`` is an asynchronous iterator that yields control.
275+
Stopping in the middle of a fragmented message will cause a
276+
protocol error and the connection will be closed.
277+
259278
When the connection is closed, :meth:`send` raises
260279
:exc:`~websockets.exceptions.ConnectionClosed`. Specifically, it
261280
raises :exc:`~websockets.exceptions.ConnectionClosedOK` after a normal
@@ -272,6 +291,11 @@ async def send(self, message: Union[Data, Iterable[Data]]) -> None:
272291
TypeError: If ``message`` doesn't have a supported type.
273292
274293
"""
294+
# While sending a fragmented message, prevent sending other messages
295+
# until all fragments are sent.
296+
while self.fragmented_send_waiter is not None:
297+
await asyncio.shield(self.fragmented_send_waiter)
298+
275299
# Unfragmented message -- this case must be handled first because
276300
# strings and bytes-like objects are iterable.
277301

src/websockets/legacy/protocol.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,7 @@ async def send(
616616
Stopping in the middle of a fragmented message will cause a
617617
protocol error and the connection will be closed.
618618
619+
619620
When the connection is closed, :meth:`send` raises
620621
:exc:`~websockets.exceptions.ConnectionClosed`. Specifically, it
621622
raises :exc:`~websockets.exceptions.ConnectionClosedOK` after a normal
@@ -624,8 +625,7 @@ async def send(
624625
error or a network failure.
625626
626627
Args:
627-
message (Union[Data, Iterable[Data], AsyncIterable[Data]): message
628-
to send.
628+
message: message to send.
629629
630630
Raises:
631631
ConnectionClosed: When the connection is closed.

src/websockets/sync/connection.py

-2
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@
2121

2222
__all__ = ["Connection"]
2323

24-
logger = logging.getLogger(__name__)
25-
2624

2725
class Connection:
2826
"""

0 commit comments

Comments
 (0)