netutil.bind_sockets: add `reuse_port` option to set SO_REUSEPORT

This option is False by default, because not all platforms support
it.

Signed-off-by: Anton Tiurin <noxiouz@yandex.ru>
This commit is contained in:
Anton Tiurin 2015-09-09 14:36:21 +03:00
parent 88eefe9434
commit fffaa2f7f7
3 changed files with 25 additions and 4 deletions

View File

@ -111,7 +111,7 @@ _DEFAULT_BACKLOG = 128
def bind_sockets(port, address=None, family=socket.AF_UNSPEC,
backlog=_DEFAULT_BACKLOG, flags=None):
backlog=_DEFAULT_BACKLOG, flags=None, reuse_port=False):
"""Creates listening sockets bound to the given port and address.
Returns a list of socket objects (multiple sockets are returned if
@ -130,7 +130,14 @@ def bind_sockets(port, address=None, family=socket.AF_UNSPEC,
``flags`` is a bitmask of AI_* flags to `~socket.getaddrinfo`, like
``socket.AI_PASSIVE | socket.AI_NUMERICHOST``.
``resuse_port`` option sets ``SO_REUSEPORT`` option for every socket
in the list. If your platform doesn't support this option ValueError will
be raised.
"""
if reuse_port and not hasattr(socket, "SO_REUSEPORT"):
raise ValueError("the platform doesn't support SO_REUSEPORT")
sockets = []
if address == "":
address = None
@ -165,6 +172,8 @@ def bind_sockets(port, address=None, family=socket.AF_UNSPEC,
set_close_exec(sock.fileno())
if os.name != 'nt':
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
if reuse_port:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
if af == socket.AF_INET6:
# On linux, ipv6 sockets accept ipv4 too by default,
# but this makes it impossible to bind to both

View File

@ -9,7 +9,7 @@ import time
from tornado.netutil import BlockingResolver, ThreadedResolver, is_valid_ip, bind_sockets
from tornado.stack_context import ExceptionStackContext
from tornado.testing import AsyncTestCase, gen_test
from tornado.testing import AsyncTestCase, gen_test, bind_unused_port
from tornado.test.util import unittest, skipIfNoNetwork
try:
@ -200,3 +200,14 @@ class TestPortAllocation(unittest.TestCase):
finally:
for sock in sockets:
sock.close()
@unittest.skipIf(not hasattr(socket, "SO_REUSEPORT"), "SO_REUSEPORT is not supported")
def test_reuse_port(self):
socket, port = bind_unused_port(reuse_port=True)
try:
sockets = bind_sockets(port, 'localhost', reuse_port=True)
self.assertTrue(all(s.getsockname()[1] == port for s in sockets))
finally:
socket.close()
for sock in sockets:
sock.close()

View File

@ -85,12 +85,13 @@ def get_unused_port():
return port
def bind_unused_port():
def bind_unused_port(reuse_port=False):
"""Binds a server socket to an available port on localhost.
Returns a tuple (socket, port).
"""
[sock] = netutil.bind_sockets(None, 'localhost', family=socket.AF_INET)
[sock] = netutil.bind_sockets(None, 'localhost', family=socket.AF_INET,
reuse_port=reuse_port)
port = sock.getsockname()[1]
return sock, port