]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
netutil.bind_sockets: add `reuse_port` option to set SO_REUSEPORT 1514/head
authorAnton Tiurin <noxiouz@yandex.ru>
Wed, 9 Sep 2015 11:36:21 +0000 (14:36 +0300)
committerAnton Tiurin <noxiouz@yandex.ru>
Fri, 11 Sep 2015 22:40:47 +0000 (01:40 +0300)
This option is False by default, because not all platforms support
it.

Signed-off-by: Anton Tiurin <noxiouz@yandex.ru>
tornado/netutil.py
tornado/test/netutil_test.py
tornado/testing.py

index 9aa292c41729d45c9dc7e046df5a3ba892d9220f..4fc8d04d9c75aba25ab55920bee242dbc1a8a968 100644 (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
index 7d9cad34a0491b579813dff135b7d8db560a86a0..9ef5f7cfe13e36a0c4ba811d6262574ef5f08f77 100644 (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()
index 304f22db3fda25979f0de155d4cdcadc107efbed..f5e9f153581502ca9cbcd9dddc8ea306291668b6 100644 (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