Skip to content

Commit b9bf913

Browse files
committed
Issue #23972: updates to asyncio datagram API. By Chris Laws.
1 parent d17e978 commit b9bf913

File tree

7 files changed

+385
-74
lines changed

7 files changed

+385
-74
lines changed

Doc/library/asyncio-eventloop.rst

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -283,17 +283,50 @@ Creating connections
283283
(:class:`StreamReader`, :class:`StreamWriter`) instead of a protocol.
284284

285285

286-
.. coroutinemethod:: BaseEventLoop.create_datagram_endpoint(protocol_factory, local_addr=None, remote_addr=None, \*, family=0, proto=0, flags=0)
286+
.. coroutinemethod:: BaseEventLoop.create_datagram_endpoint(protocol_factory, local_addr=None, remote_addr=None, \*, family=0, proto=0, flags=0, reuse_address=None, reuse_port=None, allow_broadcast=None, sock=None)
287287

288288
Create datagram connection: socket family :py:data:`~socket.AF_INET` or
289289
:py:data:`~socket.AF_INET6` depending on *host* (or *family* if specified),
290-
socket type :py:data:`~socket.SOCK_DGRAM`.
290+
socket type :py:data:`~socket.SOCK_DGRAM`. *protocol_factory* must be a
291+
callable returning a :ref:`protocol <asyncio-protocol>` instance.
291292

292293
This method is a :ref:`coroutine <coroutine>` which will try to
293294
establish the connection in the background. When successful, the
294295
coroutine returns a ``(transport, protocol)`` pair.
295296

296-
See the :meth:`BaseEventLoop.create_connection` method for parameters.
297+
Options changing how the connection is created:
298+
299+
* *local_addr*, if given, is a ``(local_host, local_port)`` tuple used
300+
to bind the socket to locally. The *local_host* and *local_port*
301+
are looked up using :meth:`getaddrinfo`.
302+
303+
* *remote_addr*, if given, is a ``(remote_host, remote_port)`` tuple used
304+
to connect the socket to a remote address. The *remote_host* and
305+
*remote_port* are looked up using :meth:`getaddrinfo`.
306+
307+
* *family*, *proto*, *flags* are the optional address family, protocol
308+
and flags to be passed through to :meth:`getaddrinfo` for *host*
309+
resolution. If given, these should all be integers from the
310+
corresponding :mod:`socket` module constants.
311+
312+
* *reuse_address* tells the kernel to reuse a local socket in
313+
TIME_WAIT state, without waiting for its natural timeout to
314+
expire. If not specified will automatically be set to True on
315+
UNIX.
316+
317+
* *reuse_port* tells the kernel to allow this endpoint to be bound to the
318+
same port as other existing endpoints are bound to, so long as they all
319+
set this flag when being created. This option is not supported on Windows
320+
and some UNIX's. If the :py:data:`~socket.SO_REUSEPORT` constant is not
321+
defined then this capability is unsupported.
322+
323+
* *allow_broadcast* tells the kernel to allow this endpoint to send
324+
messages to the broadcast address.
325+
326+
* *sock* can optionally be specified in order to use a preexisting,
327+
already connected, :class:`socket.socket` object to be used by the
328+
transport. If specified, *local_addr* and *remote_addr* should be omitted
329+
(must be :const:`None`).
297330

298331
On Windows with :class:`ProactorEventLoop`, this method is not supported.
299332

@@ -320,7 +353,7 @@ Creating connections
320353
Creating listening connections
321354
------------------------------
322355

323-
.. coroutinemethod:: BaseEventLoop.create_server(protocol_factory, host=None, port=None, \*, family=socket.AF_UNSPEC, flags=socket.AI_PASSIVE, sock=None, backlog=100, ssl=None, reuse_address=None)
356+
.. coroutinemethod:: BaseEventLoop.create_server(protocol_factory, host=None, port=None, \*, family=socket.AF_UNSPEC, flags=socket.AI_PASSIVE, sock=None, backlog=100, ssl=None, reuse_address=None, reuse_port=None)
324357

325358
Create a TCP server (socket type :data:`~socket.SOCK_STREAM`) bound to
326359
*host* and *port*.
@@ -359,6 +392,11 @@ Creating listening connections
359392
expire. If not specified will automatically be set to True on
360393
UNIX.
361394

395+
* *reuse_port* tells the kernel to allow this endpoint to be bound to the
396+
same port as other existing endpoints are bound to, so long as they all
397+
set this flag when being created. This option is not supported on
398+
Windows.
399+
362400
This method is a :ref:`coroutine <coroutine>`.
363401

364402
On Windows with :class:`ProactorEventLoop`, SSL/TLS is not supported.

Lib/asyncio/base_events.py

Lines changed: 108 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -700,75 +700,109 @@ def _create_connection_transport(self, sock, protocol_factory, ssl,
700700
@coroutine
701701
def create_datagram_endpoint(self, protocol_factory,
702702
local_addr=None, remote_addr=None, *,
703-
family=0, proto=0, flags=0):
703+
family=0, proto=0, flags=0,
704+
reuse_address=None, reuse_port=None,
705+
allow_broadcast=None, sock=None):
704706
"""Create datagram connection."""
705-
if not (local_addr or remote_addr):
706-
if family == 0:
707-
raise ValueError('unexpected address family')
708-
addr_pairs_info = (((family, proto), (None, None)),)
709-
else:
710-
# join address by (family, protocol)
711-
addr_infos = collections.OrderedDict()
712-
for idx, addr in ((0, local_addr), (1, remote_addr)):
713-
if addr is not None:
714-
assert isinstance(addr, tuple) and len(addr) == 2, (
715-
'2-tuple is expected')
716-
717-
infos = yield from self.getaddrinfo(
718-
*addr, family=family, type=socket.SOCK_DGRAM,
719-
proto=proto, flags=flags)
720-
if not infos:
721-
raise OSError('getaddrinfo() returned empty list')
722-
723-
for fam, _, pro, _, address in infos:
724-
key = (fam, pro)
725-
if key not in addr_infos:
726-
addr_infos[key] = [None, None]
727-
addr_infos[key][idx] = address
728-
729-
# each addr has to have info for each (family, proto) pair
730-
addr_pairs_info = [
731-
(key, addr_pair) for key, addr_pair in addr_infos.items()
732-
if not ((local_addr and addr_pair[0] is None) or
733-
(remote_addr and addr_pair[1] is None))]
734-
735-
if not addr_pairs_info:
736-
raise ValueError('can not get address information')
737-
738-
exceptions = []
739-
740-
for ((family, proto),
741-
(local_address, remote_address)) in addr_pairs_info:
742-
sock = None
707+
if sock is not None:
708+
if (local_addr or remote_addr or
709+
family or proto or flags or
710+
reuse_address or reuse_port or allow_broadcast):
711+
# show the problematic kwargs in exception msg
712+
opts = dict(local_addr=local_addr, remote_addr=remote_addr,
713+
family=family, proto=proto, flags=flags,
714+
reuse_address=reuse_address, reuse_port=reuse_port,
715+
allow_broadcast=allow_broadcast)
716+
problems = ', '.join(
717+
'{}={}'.format(k, v) for k, v in opts.items() if v)
718+
raise ValueError(
719+
'socket modifier keyword arguments can not be used '
720+
'when sock is specified. ({})'.format(problems))
721+
sock.setblocking(False)
743722
r_addr = None
744-
try:
745-
sock = socket.socket(
746-
family=family, type=socket.SOCK_DGRAM, proto=proto)
747-
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
748-
sock.setblocking(False)
749-
750-
if local_addr:
751-
sock.bind(local_address)
752-
if remote_addr:
753-
yield from self.sock_connect(sock, remote_address)
754-
r_addr = remote_address
755-
except OSError as exc:
756-
if sock is not None:
757-
sock.close()
758-
exceptions.append(exc)
759-
except:
760-
if sock is not None:
761-
sock.close()
762-
raise
763-
else:
764-
break
765723
else:
766-
raise exceptions[0]
724+
if not (local_addr or remote_addr):
725+
if family == 0:
726+
raise ValueError('unexpected address family')
727+
addr_pairs_info = (((family, proto), (None, None)),)
728+
else:
729+
# join address by (family, protocol)
730+
addr_infos = collections.OrderedDict()
731+
for idx, addr in ((0, local_addr), (1, remote_addr)):
732+
if addr is not None:
733+
assert isinstance(addr, tuple) and len(addr) == 2, (
734+
'2-tuple is expected')
735+
736+
infos = yield from self.getaddrinfo(
737+
*addr, family=family, type=socket.SOCK_DGRAM,
738+
proto=proto, flags=flags)
739+
if not infos:
740+
raise OSError('getaddrinfo() returned empty list')
741+
742+
for fam, _, pro, _, address in infos:
743+
key = (fam, pro)
744+
if key not in addr_infos:
745+
addr_infos[key] = [None, None]
746+
addr_infos[key][idx] = address
747+
748+
# each addr has to have info for each (family, proto) pair
749+
addr_pairs_info = [
750+
(key, addr_pair) for key, addr_pair in addr_infos.items()
751+
if not ((local_addr and addr_pair[0] is None) or
752+
(remote_addr and addr_pair[1] is None))]
753+
754+
if not addr_pairs_info:
755+
raise ValueError('can not get address information')
756+
757+
exceptions = []
758+
759+
if reuse_address is None:
760+
reuse_address = os.name == 'posix' and sys.platform != 'cygwin'
761+
762+
for ((family, proto),
763+
(local_address, remote_address)) in addr_pairs_info:
764+
sock = None
765+
r_addr = None
766+
try:
767+
sock = socket.socket(
768+
family=family, type=socket.SOCK_DGRAM, proto=proto)
769+
if reuse_address:
770+
sock.setsockopt(
771+
socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
772+
if reuse_port:
773+
if not hasattr(socket, 'SO_REUSEPORT'):
774+
raise ValueError(
775+
'reuse_port not supported by socket module')
776+
else:
777+
sock.setsockopt(
778+
socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
779+
if allow_broadcast:
780+
sock.setsockopt(
781+
socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
782+
sock.setblocking(False)
783+
784+
if local_addr:
785+
sock.bind(local_address)
786+
if remote_addr:
787+
yield from self.sock_connect(sock, remote_address)
788+
r_addr = remote_address
789+
except OSError as exc:
790+
if sock is not None:
791+
sock.close()
792+
exceptions.append(exc)
793+
except:
794+
if sock is not None:
795+
sock.close()
796+
raise
797+
else:
798+
break
799+
else:
800+
raise exceptions[0]
767801

768802
protocol = protocol_factory()
769803
waiter = futures.Future(loop=self)
770-
transport = self._make_datagram_transport(sock, protocol, r_addr,
771-
waiter)
804+
transport = self._make_datagram_transport(
805+
sock, protocol, r_addr, waiter)
772806
if self._debug:
773807
if local_addr:
774808
logger.info("Datagram endpoint local_addr=%r remote_addr=%r "
@@ -804,7 +838,8 @@ def create_server(self, protocol_factory, host=None, port=None,
804838
sock=None,
805839
backlog=100,
806840
ssl=None,
807-
reuse_address=None):
841+
reuse_address=None,
842+
reuse_port=None):
808843
"""Create a TCP server.
809844
810845
The host parameter can be a string, in that case the TCP server is bound
@@ -857,8 +892,15 @@ def create_server(self, protocol_factory, host=None, port=None,
857892
continue
858893
sockets.append(sock)
859894
if reuse_address:
860-
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,
861-
True)
895+
sock.setsockopt(
896+
socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
897+
if reuse_port:
898+
if not hasattr(socket, 'SO_REUSEPORT'):
899+
raise ValueError(
900+
'reuse_port not supported by socket module')
901+
else:
902+
sock.setsockopt(
903+
socket.SOL_SOCKET, socket.SO_REUSEPORT, True)
862904
# Disable IPv4/IPv6 dual stack support (enabled by
863905
# default on Linux) which makes a single socket
864906
# listen on both address families.

Lib/asyncio/events.py

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,8 @@ def create_connection(self, protocol_factory, host=None, port=None, *,
297297

298298
def create_server(self, protocol_factory, host=None, port=None, *,
299299
family=socket.AF_UNSPEC, flags=socket.AI_PASSIVE,
300-
sock=None, backlog=100, ssl=None, reuse_address=None):
300+
sock=None, backlog=100, ssl=None, reuse_address=None,
301+
reuse_port=None):
301302
"""A coroutine which creates a TCP server bound to host and port.
302303
303304
The return value is a Server object which can be used to stop
@@ -327,6 +328,11 @@ def create_server(self, protocol_factory, host=None, port=None, *,
327328
TIME_WAIT state, without waiting for its natural timeout to
328329
expire. If not specified will automatically be set to True on
329330
UNIX.
331+
332+
reuse_port tells the kernel to allow this endpoint to be bound to
333+
the same port as other existing endpoints are bound to, so long as
334+
they all set this flag when being created. This option is not
335+
supported on Windows.
330336
"""
331337
raise NotImplementedError
332338

@@ -358,7 +364,37 @@ def create_unix_server(self, protocol_factory, path, *,
358364

359365
def create_datagram_endpoint(self, protocol_factory,
360366
local_addr=None, remote_addr=None, *,
361-
family=0, proto=0, flags=0):
367+
family=0, proto=0, flags=0,
368+
reuse_address=None, reuse_port=None,
369+
allow_broadcast=None, sock=None):
370+
"""A coroutine which creates a datagram endpoint.
371+
372+
This method will try to establish the endpoint in the background.
373+
When successful, the coroutine returns a (transport, protocol) pair.
374+
375+
protocol_factory must be a callable returning a protocol instance.
376+
377+
socket family AF_INET or socket.AF_INET6 depending on host (or
378+
family if specified), socket type SOCK_DGRAM.
379+
380+
reuse_address tells the kernel to reuse a local socket in
381+
TIME_WAIT state, without waiting for its natural timeout to
382+
expire. If not specified it will automatically be set to True on
383+
UNIX.
384+
385+
reuse_port tells the kernel to allow this endpoint to be bound to
386+
the same port as other existing endpoints are bound to, so long as
387+
they all set this flag when being created. This option is not
388+
supported on Windows and some UNIX's. If the
389+
:py:data:`~socket.SO_REUSEPORT` constant is not defined then this
390+
capability is unsupported.
391+
392+
allow_broadcast tells the kernel to allow this endpoint to send
393+
messages to the broadcast address.
394+
395+
sock can optionally be specified in order to use a preexisting
396+
socket object.
397+
"""
362398
raise NotImplementedError
363399

364400
# Pipes and subprocesses.

0 commit comments

Comments
 (0)