]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Pull process forking out of HTTPServer into a new module
authorBen Darnell <ben@bendarnell.com>
Mon, 4 Jul 2011 23:32:14 +0000 (16:32 -0700)
committerBen Darnell <ben@bendarnell.com>
Mon, 4 Jul 2011 23:32:14 +0000 (16:32 -0700)
tornado/httpserver.py
tornado/process.py [new file with mode: 0644]
tornado/test/import_test.py
website/sphinx/process.rst [new file with mode: 0644]
website/sphinx/utilities.rst

index 8c3467c583c9af4da43239f23f50829124c2b009..e590d678bc46f255237fb75b3c364d4c8be1b5a3 100644 (file)
@@ -36,6 +36,7 @@ from tornado import httputil
 from tornado import ioloop
 from tornado import iostream
 from tornado import netutil
+from tornado import process
 from tornado import stack_context
 from tornado.util import b, bytes_type
 
@@ -52,25 +53,6 @@ try:
 except ImportError:
     ssl = None
 
-try:
-    import multiprocessing # Python 2.6+
-except ImportError:
-    multiprocessing = None
-
-def _cpu_count():
-    if multiprocessing is not None:
-        try:
-            return multiprocessing.cpu_count()
-        except NotImplementedError:
-            pass
-    try:
-        return os.sysconf("SC_NPROCESSORS_CONF")
-    except ValueError:
-        pass
-    logging.error("Could not detect number of processors; "
-                  "running with one process")
-    return 1
-
 
 class HTTPServer(object):
     r"""A non-blocking, single-threaded HTTP server.
@@ -214,40 +196,13 @@ class HTTPServer(object):
         """
         assert not self._started
         self._started = True
-        if num_processes is None or num_processes <= 0:
-            num_processes = _cpu_count()
-        if num_processes > 1 and ioloop.IOLoop.initialized():
-            logging.error("Cannot run in multiple processes: IOLoop instance "
-                          "has already been initialized. You cannot call "
-                          "IOLoop.instance() before calling start()")
-            num_processes = 1
-        if num_processes > 1:
-            logging.info("Pre-forking %d server processes", num_processes)
-            for i in range(num_processes):
-                if os.fork() == 0:
-                    import random
-                    from binascii import hexlify
-                    try:
-                        # If available, use the same method as
-                        # random.py
-                        seed = long(hexlify(os.urandom(16)), 16)
-                    except NotImplementedError:
-                        # Include the pid to avoid initializing two
-                        # processes to the same value
-                        seed(int(time.time() * 1000) ^ os.getpid())
-                    random.seed(seed)
-                    self.io_loop = ioloop.IOLoop.instance()
-                    for fd in self._sockets.keys():
-                        self.io_loop.add_handler(fd, self._handle_events,
-                                                 ioloop.IOLoop.READ)
-                    return
-            os.waitpid(-1, 0)
-        else:
-            if not self.io_loop:
-                self.io_loop = ioloop.IOLoop.instance()
-            for fd in self._sockets.keys():
-                self.io_loop.add_handler(fd, self._handle_events,
-                                         ioloop.IOLoop.READ)
+        if num_processes != 1:
+            process.fork_processes(num_processes)
+        if not self.io_loop:
+            self.io_loop = ioloop.IOLoop.instance()
+        for fd in self._sockets.keys():
+            self.io_loop.add_handler(fd, self._handle_events,
+                                     ioloop.IOLoop.READ)
 
     def stop(self):
         """Stops listening for new connections.
diff --git a/tornado/process.py b/tornado/process.py
new file mode 100644 (file)
index 0000000..8658329
--- /dev/null
@@ -0,0 +1,86 @@
+#!/usr/bin/env python
+#
+# Copyright 2011 Facebook
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""Utilities for working with multiple processes."""
+
+import logging
+import os
+import time
+
+from tornado import ioloop
+
+try:
+    import multiprocessing # Python 2.6+
+except ImportError:
+    multiprocessing = None
+
+def cpu_count():
+    """Returns the number of processors on this machine."""
+    if multiprocessing is not None:
+        try:
+            return multiprocessing.cpu_count()
+        except NotImplementedError:
+            pass
+    try:
+        return os.sysconf("SC_NPROCESSORS_CONF")
+    except ValueError:
+        pass
+    logging.error("Could not detect number of processors; assuming 1")
+    return 1
+
+_processes_forked = False
+
+def fork_processes(num_processes):
+    """Starts multiple worker processes.
+
+    If num_processes is None or <= 0, we detect the number of cores
+    available on this machine and fork that number of child
+    processes. If num_processes is given and > 0, we fork that
+    specific number of sub-processes.
+
+    Since we use processes and not threads, there is no shared memory
+    between any server code.
+
+    Note that multiple processes are not compatible with the autoreload
+    module (or the debug=True option to tornado.web.Application).
+    When using multiple processes, no IOLoops can be created or
+    referenced until after the call to fork_processes.
+    """
+    global _processes_forked
+    assert not _processes_forked
+    _processes_forked = True
+    if num_processes is None or num_processes <= 0:
+        num_processes = cpu_count()
+    if ioloop.IOLoop.initialized():
+        raise RuntimeError("Cannot run in multiple processes: IOLoop instance "
+                           "has already been initialized. You cannot call "
+                           "IOLoop.instance() before calling start_processes()")
+    logging.info("Starting %d server processes", num_processes)
+    for i in range(num_processes):
+        if os.fork() == 0:
+            import random
+            from binascii import hexlify
+            try:
+                # If available, use the same method as
+                # random.py
+                seed = long(hexlify(os.urandom(16)), 16)
+            except NotImplementedError:
+                # Include the pid to avoid initializing two
+                # processes to the same value
+                seed(int(time.time() * 1000) ^ os.getpid())
+            random.seed(seed)
+            return
+    os.waitpid(-1, 0)
index a94c111ee9a5eeb80383fd9c85d274f3c9a027ab..7d300c525d19f55185e05eec0598d7e27afc0057 100644 (file)
@@ -18,6 +18,7 @@ class ImportTest(unittest.TestCase):
         import tornado.locale
         import tornado.options
         import tornado.netutil
+        import tornado.process
         import tornado.simple_httpclient
         import tornado.stack_context
         import tornado.template
diff --git a/website/sphinx/process.rst b/website/sphinx/process.rst
new file mode 100644 (file)
index 0000000..c9ce63b
--- /dev/null
@@ -0,0 +1,5 @@
+``tornado.process`` --- Utilities for multiple processes
+========================================================
+
+.. automodule:: tornado.process
+   :members:
index 89c990807c210c540c4fe17e75a5c67e94b037ec..f007bc609522c2ef0ef50a0d01fcfd031e6740c3 100644 (file)
@@ -6,6 +6,7 @@ Utilities
    autoreload
    httputil
    options
+   process
    stack_context
    testing