]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
tcpserver,web: Pass more arguments through listen. 3148/head
authorBen Darnell <ben@bendarnell.com>
Fri, 3 Jun 2022 15:40:31 +0000 (11:40 -0400)
committerBen Darnell <ben@bendarnell.com>
Fri, 3 Jun 2022 19:00:47 +0000 (15:00 -0400)
In particular, this makes it easier to use reuse_port.

tornado/tcpserver.py
tornado/test/tcpserver_test.py
tornado/web.py

index 476ffc936f731d8c341c5e8c00b55f295ed3db60..9065b62e89932fe768626982ee630dc659027228 100644 (file)
@@ -24,7 +24,12 @@ from tornado import gen
 from tornado.log import app_log
 from tornado.ioloop import IOLoop
 from tornado.iostream import IOStream, SSLIOStream
-from tornado.netutil import bind_sockets, add_accept_handler, ssl_wrap_socket
+from tornado.netutil import (
+    bind_sockets,
+    add_accept_handler,
+    ssl_wrap_socket,
+    _DEFAULT_BACKLOG,
+)
 from tornado import process
 from tornado.util import errno_from_exception
 
@@ -140,15 +145,38 @@ class TCPServer(object):
                     'keyfile "%s" does not exist' % self.ssl_options["keyfile"]
                 )
 
-    def listen(self, port: int, address: str = "") -> None:
+    def listen(
+        self,
+        port: int,
+        address: Optional[str] = None,
+        family: socket.AddressFamily = socket.AF_UNSPEC,
+        backlog: int = _DEFAULT_BACKLOG,
+        flags: Optional[int] = None,
+        reuse_port: bool = False,
+    ) -> None:
         """Starts accepting connections on the given port.
 
         This method may be called more than once to listen on multiple ports.
         `listen` takes effect immediately; it is not necessary to call
-        `TCPServer.start` afterwards.  It is, however, necessary to start
-        the `.IOLoop`.
+        `TCPServer.start` afterwards.  It is, however, necessary to start the
+        event loop if it is not already running.
+
+        All arguments have the same meaning as in
+        `tornado.netutil.bind_sockets`.
+
+        .. versionchanged:: 6.2
+
+           Added ``family``, ``backlog``, ``flags``, and ``reuse_port``
+           arguments to match `tornado.netutil.bind_sockets`.
         """
-        sockets = bind_sockets(port, address=address)
+        sockets = bind_sockets(
+            port,
+            address=address,
+            family=family,
+            backlog=backlog,
+            flags=flags,
+            reuse_port=reuse_port,
+        )
         self.add_sockets(sockets)
 
     def add_sockets(self, sockets: Iterable[socket.socket]) -> None:
@@ -175,34 +203,43 @@ class TCPServer(object):
         port: int,
         address: Optional[str] = None,
         family: socket.AddressFamily = socket.AF_UNSPEC,
-        backlog: int = 128,
+        backlog: int = _DEFAULT_BACKLOG,
+        flags: Optional[int] = None,
         reuse_port: bool = False,
     ) -> None:
         """Binds this server to the given port on the given address.
 
-        To start the server, call `start`. If you want to run this server
-        in a single process, you can call `listen` as a shortcut to the
-        sequence of `bind` and `start` calls.
+        To start the server, call `start`. If you want to run this server in a
+        single process, you can call `listen` as a shortcut to the sequence of
+        `bind` and `start` calls.
 
         Address may be either an IP address or hostname.  If it's a hostname,
-        the server will listen on all IP addresses associated with the
-        name.  Address may be an empty string or None to listen on all
-        available interfaces.  Family may be set to either `socket.AF_INET`
-        or `socket.AF_INET6` to restrict to IPv4 or IPv6 addresses, otherwise
-        both will be used if available.
+        the server will listen on all IP addresses associated with the name.
+        Address may be an empty string or None to listen on all available
+        interfaces.  Family may be set to either `socket.AF_INET` or
+        `socket.AF_INET6` to restrict to IPv4 or IPv6 addresses, otherwise both
+        will be used if available.
 
-        The ``backlog`` argument has the same meaning as for
-        `socket.listen <socket.socket.listen>`. The ``reuse_port`` argument
-        has the same meaning as for `.bind_sockets`.
+        The ``backlog`` argument has the same meaning as for `socket.listen
+        <socket.socket.listen>`. The ``reuse_port`` argument has the same
+        meaning as for `.bind_sockets`.
 
-        This method may be called multiple times prior to `start` to listen
-        on multiple ports or interfaces.
+        This method may be called multiple times prior to `start` to listen on
+        multiple ports or interfaces.
 
         .. versionchanged:: 4.4
            Added the ``reuse_port`` argument.
+
+        .. versionchanged:: 6.2
+           Added the ``flags`` argument to match `.bind_sockets`.
         """
         sockets = bind_sockets(
-            port, address=address, family=family, backlog=backlog, reuse_port=reuse_port
+            port,
+            address=address,
+            family=family,
+            backlog=backlog,
+            flags=flags,
+            reuse_port=reuse_port,
         )
         if self._started:
             self.add_sockets(sockets)
index fc9e98a23a15623d14c1695df8079090496945cc..8b59db577efa41d93df22b1927a6e4ad0260e079 100644 (file)
@@ -125,14 +125,15 @@ class TestMultiprocess(unittest.TestCase):
         try:
             result = subprocess.run(
                 sys.executable,
-                capture_output=True,
+                stdout=subprocess.PIPE,
+                stderr=subprocess.STDOUT,
                 input=code,
                 encoding="utf8",
                 check=True,
             )
         except subprocess.CalledProcessError as e:
             raise RuntimeError(
-                f"Process returned {e.returncode} stdout={e.stdout}"
+                f"Process returned {e.returncode} output={e.stdout}"
             ) from e
         return result.stdout
 
@@ -189,3 +190,27 @@ class TestMultiprocess(unittest.TestCase):
         )
         out = self.run_subproc(code)
         self.assertEqual("".join(sorted(out)), "012")
+
+    def test_reuse_port(self):
+        code = textwrap.dedent(
+            """
+            import socket
+            from tornado.ioloop import IOLoop
+            from tornado.netutil import bind_sockets
+            from tornado.process import task_id, fork_processes
+            from tornado.tcpserver import TCPServer
+
+            # Pick an unused port which we will be able to bind to multiple times.
+            (sock,) = bind_sockets(0, address='127.0.0.1',
+                family=socket.AF_INET, reuse_port=True)
+            port = sock.getsockname()[1]
+
+            fork_processes(3)
+            server = TCPServer()
+            server.listen(port, address='127.0.0.1', reuse_port=True)
+            IOLoop.current().run_sync(lambda: None)
+            print(task_id(), end='')
+            """
+        )
+        out = self.run_subproc(code)
+        self.assertEqual("".join(sorted(out)), "012")
index 2dab485a480007e972d8b8795dc9ecb50231c9f9..f3eb6e1e7bdf93d0151e3daca09e49059c3ee3f9 100644 (file)
@@ -72,6 +72,7 @@ import mimetypes
 import numbers
 import os.path
 import re
+import socket
 import sys
 import threading
 import time
@@ -90,6 +91,7 @@ from tornado import iostream
 import tornado.locale
 from tornado import locale
 from tornado.log import access_log, app_log, gen_log
+import tornado.netutil
 from tornado import template
 from tornado.escape import utf8, _unicode
 from tornado.routing import (
@@ -2093,7 +2095,17 @@ class Application(ReversibleRouter):
 
             autoreload.start()
 
-    def listen(self, port: int, address: str = "", **kwargs: Any) -> HTTPServer:
+    def listen(
+        self,
+        port: int,
+        address: Optional[str] = None,
+        *,
+        family: socket.AddressFamily = socket.AF_UNSPEC,
+        backlog: int = tornado.netutil._DEFAULT_BACKLOG,
+        flags: Optional[int] = None,
+        reuse_port: bool = False,
+        **kwargs: Any
+    ) -> HTTPServer:
         """Starts an HTTP server for this application on the given port.
 
         This is a convenience alias for creating an `.HTTPServer`
@@ -2111,9 +2123,20 @@ class Application(ReversibleRouter):
 
         .. versionchanged:: 4.3
            Now returns the `.HTTPServer` object.
+
+        .. versionchanged:: 6.2
+           Added support for new keyword arguments in `.TCPServer.listen`,
+           including ``reuse_port``.
         """
         server = HTTPServer(self, **kwargs)
-        server.listen(port, address)
+        server.listen(
+            port,
+            address=address,
+            family=family,
+            backlog=backlog,
+            flags=flags,
+            reuse_port=reuse_port,
+        )
         return server
 
     def add_handlers(self, host_pattern: str, host_handlers: _RuleList) -> None: