--- /dev/null
+# Copyright (C) 2007 Giampaolo Rodola' <g.rodola@gmail.com>.\r
+# Use of this source code is governed by MIT license that can be\r
+# found in the LICENSE file.\r
+\r
+"""Process utils."""\r
+\r
+import errno\r
+import os\r
+import sys\r
+import time\r
+from binascii import hexlify\r
+try:\r
+ import multiprocessing\r
+except ImportError:\r
+ multiprocessing = None\r
+\r
+from ._compat import long\r
+from .log import logger\r
+\r
+\r
+_task_id = None\r
+\r
+\r
+def cpu_count():\r
+ """Returns the number of processors on this machine."""\r
+ if multiprocessing is None:\r
+ return 1\r
+ try:\r
+ return multiprocessing.cpu_count()\r
+ except NotImplementedError:\r
+ pass\r
+ try:\r
+ return os.sysconf("SC_NPROCESSORS_CONF")\r
+ except (AttributeError, ValueError):\r
+ pass\r
+ return 1\r
+\r
+\r
+def _reseed_random():\r
+ if 'random' not in sys.modules:\r
+ return\r
+ import random\r
+ # If os.urandom is available, this method does the same thing as\r
+ # random.seed (at least as of python 2.6). If os.urandom is not\r
+ # available, we mix in the pid in addition to a timestamp.\r
+ try:\r
+ seed = long(hexlify(os.urandom(16)), 16)\r
+ except NotImplementedError:\r
+ seed = int(time.time() * 1000) ^ os.getpid()\r
+ random.seed(seed)\r
+\r
+\r
+def fork_processes(number, max_restarts=100):\r
+ """Starts multiple worker processes.\r
+\r
+ If *number* is None or <= 0, we detect the number of cores available\r
+ on this machine and fork that number of child processes.\r
+ If *number* is given and > 0, we fork that specific number of\r
+ sub-processes.\r
+\r
+ Since we use processes and not threads, there is no shared memory\r
+ between any server code.\r
+\r
+ In each child process, *fork_processes* returns its *task id*, a\r
+ number between 0 and *number*. Processes that exit abnormally\r
+ (due to a signal or non-zero exit status) are restarted with the\r
+ same id (up to *max_restarts* times). In the parent process,\r
+ *fork_processes* returns None if all child processes have exited\r
+ normally, but will otherwise only exit by throwing an exception.\r
+ """\r
+ global _task_id\r
+ assert _task_id is None\r
+ if number is None or number <= 0:\r
+ number = cpu_count()\r
+ logger.info("starting %d pre-fork processes", number)\r
+ children = {}\r
+\r
+ def start_child(i):\r
+ pid = os.fork()\r
+ if pid == 0:\r
+ # child process\r
+ _reseed_random()\r
+ global _task_id\r
+ _task_id = i\r
+ return i\r
+ else:\r
+ children[pid] = i\r
+ return None\r
+\r
+ for i in range(number):\r
+ id = start_child(i)\r
+ if id is not None:\r
+ return id\r
+ num_restarts = 0\r
+ while children:\r
+ try:\r
+ pid, status = os.wait()\r
+ except OSError as e:\r
+ if e.errno == errno.EINTR:\r
+ continue\r
+ raise\r
+ if pid not in children:\r
+ continue\r
+ id = children.pop(pid)\r
+ if os.WIFSIGNALED(status):\r
+ logger.warning("child %d (pid %d) killed by signal %d, restarting",\r
+ id, pid, os.WTERMSIG(status))\r
+ elif os.WEXITSTATUS(status) != 0:\r
+ logger.warning(\r
+ "child %d (pid %d) exited with status %d, restarting",\r
+ id, pid, os.WEXITSTATUS(status))\r
+ else:\r
+ logger.info("child %d (pid %d) exited normally", id, pid)\r
+ continue\r
+ num_restarts += 1\r
+ if num_restarts > max_restarts:\r
+ raise RuntimeError("Too many child restarts, giving up")\r
+ new_id = start_child(id)\r
+ if new_id is not None:\r
+ return new_id\r
+ # All child processes exited cleanly, so exit the master process\r
+ # instead of just returning to right after the call to\r
+ # fork_processes (which will probably just start up another IOLoop\r
+ # unless the caller checks the return value).\r
+ sys.exit(0)\r