Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit bafd4b5

Browse files
asvetlovmiss-islington
authored andcommitted
bpo-29883: Asyncio proactor udp (GH-13440)
Follow-up for #1067 https://bugs.python.org/issue29883
1 parent 9ee2c26 commit bafd4b5

8 files changed

Lines changed: 838 additions & 46 deletions

File tree

Doc/library/asyncio-eventloop.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -504,15 +504,16 @@ Opening network connections
504504
transport. If specified, *local_addr* and *remote_addr* should be omitted
505505
(must be :const:`None`).
506506

507-
On Windows, with :class:`ProactorEventLoop`, this method is not supported.
508-
509507
See :ref:`UDP echo client protocol <asyncio-udp-echo-client-protocol>` and
510508
:ref:`UDP echo server protocol <asyncio-udp-echo-server-protocol>` examples.
511509

512510
.. versionchanged:: 3.4.4
513511
The *family*, *proto*, *flags*, *reuse_address*, *reuse_port,
514512
*allow_broadcast*, and *sock* parameters were added.
515513

514+
.. versionchanged:: 3.8
515+
Added support for Windows.
516+
516517
.. coroutinemethod:: loop.create_unix_connection(protocol_factory, \
517518
path=None, \*, ssl=None, sock=None, \
518519
server_hostname=None, ssl_handshake_timeout=None)

Doc/library/asyncio-platforms.rst

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,6 @@ All event loops on Windows do not support the following methods:
5353

5454
:class:`ProactorEventLoop` has the following limitations:
5555

56-
* The :meth:`loop.create_datagram_endpoint` method
57-
is not supported.
58-
5956
* The :meth:`loop.add_reader` and :meth:`loop.add_writer`
6057
methods are not supported.
6158

Lib/asyncio/proactor_events.py

Lines changed: 153 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import socket
1212
import warnings
1313
import signal
14+
import collections
1415

1516
from . import base_events
1617
from . import constants
@@ -23,6 +24,24 @@
2324
from .log import logger
2425

2526

27+
def _set_socket_extra(transport, sock):
28+
transport._extra['socket'] = trsock.TransportSocket(sock)
29+
30+
try:
31+
transport._extra['sockname'] = sock.getsockname()
32+
except socket.error:
33+
if transport._loop.get_debug():
34+
logger.warning(
35+
"getsockname() failed on %r", sock, exc_info=True)
36+
37+
if 'peername' not in transport._extra:
38+
try:
39+
transport._extra['peername'] = sock.getpeername()
40+
except socket.error:
41+
# UDP sockets may not have a peer name
42+
transport._extra['peername'] = None
43+
44+
2645
class _ProactorBasePipeTransport(transports._FlowControlMixin,
2746
transports.BaseTransport):
2847
"""Base class for pipe and socket transports."""
@@ -430,6 +449,134 @@ def _pipe_closed(self, fut):
430449
self.close()
431450

432451

452+
class _ProactorDatagramTransport(_ProactorBasePipeTransport):
453+
max_size = 256 * 1024
454+
def __init__(self, loop, sock, protocol, address=None,
455+
waiter=None, extra=None):
456+
self._address = address
457+
self._empty_waiter = None
458+
# We don't need to call _protocol.connection_made() since our base
459+
# constructor does it for us.
460+
super().__init__(loop, sock, protocol, waiter=waiter, extra=extra)
461+
462+
# The base constructor sets _buffer = None, so we set it here
463+
self._buffer = collections.deque()
464+
self._loop.call_soon(self._loop_reading)
465+
466+
def _set_extra(self, sock):
467+
_set_socket_extra(self, sock)
468+
469+
def get_write_buffer_size(self):
470+
return sum(len(data) for data, _ in self._buffer)
471+
472+
def abort(self):
473+
self._force_close(None)
474+
475+
def sendto(self, data, addr=None):
476+
if not isinstance(data, (bytes, bytearray, memoryview)):
477+
raise TypeError('data argument must be bytes-like object (%r)',
478+
type(data))
479+
480+
if not data:
481+
return
482+
483+
if self._address is not None and addr not in (None, self._address):
484+
raise ValueError(
485+
f'Invalid address: must be None or {self._address}')
486+
487+
if self._conn_lost and self._address:
488+
if self._conn_lost >= constants.LOG_THRESHOLD_FOR_CONNLOST_WRITES:
489+
logger.warning('socket.sendto() raised exception.')
490+
self._conn_lost += 1
491+
return
492+
493+
# Ensure that what we buffer is immutable.
494+
self._buffer.append((bytes(data), addr))
495+
496+
if self._write_fut is None:
497+
# No current write operations are active, kick one off
498+
self._loop_writing()
499+
# else: A write operation is already kicked off
500+
501+
self._maybe_pause_protocol()
502+
503+
def _loop_writing(self, fut=None):
504+
try:
505+
if self._conn_lost:
506+
return
507+
508+
assert fut is self._write_fut
509+
self._write_fut = None
510+
if fut:
511+
# We are in a _loop_writing() done callback, get the result
512+
fut.result()
513+
514+
if not self._buffer or (self._conn_lost and self._address):
515+
# The connection has been closed
516+
if self._closing:
517+
self._loop.call_soon(self._call_connection_lost, None)
518+
return
519+
520+
data, addr = self._buffer.popleft()
521+
if self._address is not None:
522+
self._write_fut = self._loop._proactor.send(self._sock,
523+
data)
524+
else:
525+
self._write_fut = self._loop._proactor.sendto(self._sock,
526+
data,
527+
addr=addr)
528+
except OSError as exc:
529+
self._protocol.error_received(exc)
530+
except Exception as exc:
531+
self._fatal_error(exc, 'Fatal write error on datagram transport')
532+
else:
533+
self._write_fut.add_done_callback(self._loop_writing)
534+
self._maybe_resume_protocol()
535+
536+
def _loop_reading(self, fut=None):
537+
data = None
538+
try:
539+
if self._conn_lost:
540+
return
541+
542+
assert self._read_fut is fut or (self._read_fut is None and
543+
self._closing)
544+
545+
self._read_fut = None
546+
if fut is not None:
547+
res = fut.result()
548+
549+
if self._closing:
550+
# since close() has been called we ignore any read data
551+
data = None
552+
return
553+
554+
if self._address is not None:
555+
data, addr = res, self._address
556+
else:
557+
data, addr = res
558+
559+
if self._conn_lost:
560+
return
561+
if self._address is not None:
562+
self._read_fut = self._loop._proactor.recv(self._sock,
563+
self.max_size)
564+
else:
565+
self._read_fut = self._loop._proactor.recvfrom(self._sock,
566+
self.max_size)
567+
except OSError as exc:
568+
self._protocol.error_received(exc)
569+
except exceptions.CancelledError:
570+
if not self._closing:
571+
raise
572+
else:
573+
if self._read_fut is not None:
574+
self._read_fut.add_done_callback(self._loop_reading)
575+
finally:
576+
if data:
577+
self._protocol.datagram_received(data, addr)
578+
579+
433580
class _ProactorDuplexPipeTransport(_ProactorReadPipeTransport,
434581
_ProactorBaseWritePipeTransport,
435582
transports.Transport):
@@ -455,22 +602,7 @@ def __init__(self, loop, sock, protocol, waiter=None,
455602
base_events._set_nodelay(sock)
456603

457604
def _set_extra(self, sock):
458-
self._extra['socket'] = trsock.TransportSocket(sock)
459-
460-
try:
461-
self._extra['sockname'] = sock.getsockname()
462-
except (socket.error, AttributeError):
463-
if self._loop.get_debug():
464-
logger.warning(
465-
"getsockname() failed on %r", sock, exc_info=True)
466-
467-
if 'peername' not in self._extra:
468-
try:
469-
self._extra['peername'] = sock.getpeername()
470-
except (socket.error, AttributeError):
471-
if self._loop.get_debug():
472-
logger.warning("getpeername() failed on %r",
473-
sock, exc_info=True)
605+
_set_socket_extra(self, sock)
474606

475607
def can_write_eof(self):
476608
return True
@@ -515,6 +647,11 @@ def _make_ssl_transport(
515647
extra=extra, server=server)
516648
return ssl_protocol._app_transport
517649

650+
def _make_datagram_transport(self, sock, protocol,
651+
address=None, waiter=None, extra=None):
652+
return _ProactorDatagramTransport(self, sock, protocol, address,
653+
waiter, extra)
654+
518655
def _make_duplex_pipe_transport(self, sock, protocol, waiter=None,
519656
extra=None):
520657
return _ProactorDuplexPipeTransport(self,

Lib/asyncio/windows_events.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,44 @@ def finish_recv(trans, key, ov):
483483

484484
return self._register(ov, conn, finish_recv)
485485

486+
def recvfrom(self, conn, nbytes, flags=0):
487+
self._register_with_iocp(conn)
488+
ov = _overlapped.Overlapped(NULL)
489+
try:
490+
ov.WSARecvFrom(conn.fileno(), nbytes, flags)
491+
except BrokenPipeError:
492+
return self._result((b'', None))
493+
494+
def finish_recv(trans, key, ov):
495+
try:
496+
return ov.getresult()
497+
except OSError as exc:
498+
if exc.winerror in (_overlapped.ERROR_NETNAME_DELETED,
499+
_overlapped.ERROR_OPERATION_ABORTED):
500+
raise ConnectionResetError(*exc.args)
501+
else:
502+
raise
503+
504+
return self._register(ov, conn, finish_recv)
505+
506+
def sendto(self, conn, buf, flags=0, addr=None):
507+
self._register_with_iocp(conn)
508+
ov = _overlapped.Overlapped(NULL)
509+
510+
ov.WSASendTo(conn.fileno(), buf, flags, addr)
511+
512+
def finish_send(trans, key, ov):
513+
try:
514+
return ov.getresult()
515+
except OSError as exc:
516+
if exc.winerror in (_overlapped.ERROR_NETNAME_DELETED,
517+
_overlapped.ERROR_OPERATION_ABORTED):
518+
raise ConnectionResetError(*exc.args)
519+
else:
520+
raise
521+
522+
return self._register(ov, conn, finish_send)
523+
486524
def send(self, conn, buf, flags=0):
487525
self._register_with_iocp(conn)
488526
ov = _overlapped.Overlapped(NULL)
@@ -532,6 +570,14 @@ async def accept_coro(future, conn):
532570
return future
533571

534572
def connect(self, conn, address):
573+
if conn.type == socket.SOCK_DGRAM:
574+
# WSAConnect will complete immediately for UDP sockets so we don't
575+
# need to register any IOCP operation
576+
_overlapped.WSAConnect(conn.fileno(), address)
577+
fut = self._loop.create_future()
578+
fut.set_result(None)
579+
return fut
580+
535581
self._register_with_iocp(conn)
536582
# The socket needs to be locally bound before we call ConnectEx().
537583
try:

Lib/test/test_asyncio/test_events.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1249,11 +1249,6 @@ def datagram_received(self, data, addr):
12491249
server.transport.close()
12501250

12511251
def test_create_datagram_endpoint_sock(self):
1252-
if (sys.platform == 'win32' and
1253-
isinstance(self.loop, proactor_events.BaseProactorEventLoop)):
1254-
raise unittest.SkipTest(
1255-
'UDP is not supported with proactor event loops')
1256-
12571252
sock = None
12581253
local_address = ('127.0.0.1', 0)
12591254
infos = self.loop.run_until_complete(
@@ -2004,10 +1999,6 @@ def test_writer_callback(self):
20041999
def test_writer_callback_cancel(self):
20052000
raise unittest.SkipTest("IocpEventLoop does not have add_writer()")
20062001

2007-
def test_create_datagram_endpoint(self):
2008-
raise unittest.SkipTest(
2009-
"IocpEventLoop does not have create_datagram_endpoint()")
2010-
20112002
def test_remove_fds_after_closing(self):
20122003
raise unittest.SkipTest("IocpEventLoop does not have add_reader()")
20132004
else:

0 commit comments

Comments
 (0)