Skip to content

Commit 80e1cf5

Browse files
author
whitequark
committedMar 9, 2016
Monkey-patch asyncio create_server (fixes #253).
1 parent f33baf3 commit 80e1cf5

File tree

1 file changed

+108
-0
lines changed

1 file changed

+108
-0
lines changed
 

Diff for: ‎artiq/tools.py

+108
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import time
88
import collections
99
import os
10+
import socket
11+
import itertools
1012
import atexit
1113
import string
1214

@@ -243,3 +245,109 @@ def get_windows_drives():
243245
drives.append(letter)
244246
bitmask >>= 1
245247
return drives
248+
249+
if sys.version_info[:3] == (3, 5, 1):
250+
# See https://github.com/m-labs/artiq/issues/253
251+
@asyncio.coroutines.coroutine
252+
def create_server(self, protocol_factory, host=None, port=None,
253+
*,
254+
family=socket.AF_UNSPEC,
255+
flags=socket.AI_PASSIVE,
256+
sock=None,
257+
backlog=100,
258+
ssl=None,
259+
reuse_address=None,
260+
reuse_port=None):
261+
"""Create a TCP server.
262+
The host parameter can be a string, in that case the TCP server is bound
263+
to host and port.
264+
The host parameter can also be a sequence of strings and in that case
265+
the TCP server is bound to all hosts of the sequence. If a host
266+
appears multiple times (possibly indirectly e.g. when hostnames
267+
resolve to the same IP address), the server is only bound once to that
268+
host.
269+
Return a Server object which can be used to stop the service.
270+
This method is a coroutine.
271+
"""
272+
if isinstance(ssl, bool):
273+
raise TypeError('ssl argument must be an SSLContext or None')
274+
if host is not None or port is not None:
275+
if sock is not None:
276+
raise ValueError(
277+
'host/port and sock can not be specified at the same time')
278+
279+
AF_INET6 = getattr(socket, 'AF_INET6', 0)
280+
if reuse_address is None:
281+
reuse_address = os.name == 'posix' and sys.platform != 'cygwin'
282+
sockets = []
283+
if host == '':
284+
hosts = [None]
285+
elif (isinstance(host, str) or
286+
not isinstance(host, collections.Iterable)):
287+
hosts = [host]
288+
else:
289+
hosts = host
290+
291+
fs = [self._create_server_getaddrinfo(host, port, family=family,
292+
flags=flags)
293+
for host in hosts]
294+
infos = yield from asyncio.tasks.gather(*fs, loop=self)
295+
infos = set(itertools.chain.from_iterable(infos))
296+
297+
completed = False
298+
try:
299+
for res in infos:
300+
af, socktype, proto, canonname, sa = res
301+
try:
302+
sock = socket.socket(af, socktype, proto)
303+
except socket.error:
304+
# Assume it's a bad family/type/protocol combination.
305+
if self._debug:
306+
asyncio.log.logger.warning('create_server() failed to create '
307+
'socket.socket(%r, %r, %r)',
308+
af, socktype, proto, exc_info=True)
309+
continue
310+
sockets.append(sock)
311+
if reuse_address:
312+
sock.setsockopt(
313+
socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
314+
if reuse_port:
315+
if not hasattr(socket, 'SO_REUSEPORT'):
316+
raise ValueError(
317+
'reuse_port not supported by socket module')
318+
else:
319+
sock.setsockopt(
320+
socket.SOL_SOCKET, socket.SO_REUSEPORT, True)
321+
# Disable IPv4/IPv6 dual stack support (enabled by
322+
# default on Linux) which makes a single socket
323+
# listen on both address families.
324+
if af == AF_INET6 and hasattr(socket, 'IPPROTO_IPV6'):
325+
sock.setsockopt(socket.IPPROTO_IPV6,
326+
socket.IPV6_V6ONLY,
327+
True)
328+
try:
329+
sock.bind(sa)
330+
except OSError as err:
331+
raise OSError(err.errno, 'error while attempting '
332+
'to bind on address %r: %s'
333+
% (sa, err.strerror.lower()))
334+
completed = True
335+
finally:
336+
if not completed:
337+
for sock in sockets:
338+
sock.close()
339+
else:
340+
if sock is None:
341+
raise ValueError('Neither host/port nor sock were specified')
342+
sockets = [sock]
343+
344+
server = asyncio.base_events.Server(self, sockets)
345+
for sock in sockets:
346+
sock.listen(backlog)
347+
sock.setblocking(False)
348+
self._start_serving(protocol_factory, sock, ssl, server)
349+
if self._debug:
350+
asyncio.log.logger.info("%r is serving", server)
351+
return server
352+
353+
asyncio.base_events.BaseEventLoop.create_server = create_server

0 commit comments

Comments
 (0)