1 # Copyright (C) 2007 Giampaolo Rodola' <g.rodola@gmail.com>.
\r
2 # Use of this source code is governed by MIT license that can be
\r
3 # found in the LICENSE file.
\r
11 from binascii import hexlify
\r
13 import multiprocessing
\r
15 multiprocessing = None
\r
17 from ._compat import long
\r
18 from .log import logger
\r
25 """Returns the number of processors on this machine."""
\r
26 if multiprocessing is None:
\r
29 return multiprocessing.cpu_count()
\r
30 except NotImplementedError:
\r
33 return os.sysconf("SC_NPROCESSORS_CONF")
\r
34 except (AttributeError, ValueError):
\r
39 def _reseed_random():
\r
40 if 'random' not in sys.modules:
\r
43 # If os.urandom is available, this method does the same thing as
\r
44 # random.seed (at least as of python 2.6). If os.urandom is not
\r
45 # available, we mix in the pid in addition to a timestamp.
\r
47 seed = long(hexlify(os.urandom(16)), 16)
\r
48 except NotImplementedError:
\r
49 seed = int(time.time() * 1000) ^ os.getpid()
\r
53 def fork_processes(number, max_restarts=100):
\r
54 """Starts multiple worker processes.
\r
56 If *number* is None or <= 0, we detect the number of cores available
\r
57 on this machine and fork that number of child processes.
\r
58 If *number* is given and > 0, we fork that specific number of
\r
61 Since we use processes and not threads, there is no shared memory
\r
62 between any server code.
\r
64 In each child process, *fork_processes* returns its *task id*, a
\r
65 number between 0 and *number*. Processes that exit abnormally
\r
66 (due to a signal or non-zero exit status) are restarted with the
\r
67 same id (up to *max_restarts* times). In the parent process,
\r
68 *fork_processes* returns None if all child processes have exited
\r
69 normally, but will otherwise only exit by throwing an exception.
\r
72 assert _task_id is None
\r
73 if number is None or number <= 0:
\r
74 number = cpu_count()
\r
75 logger.info("starting %d pre-fork processes", number)
\r
90 for i in range(number):
\r
97 pid, status = os.wait()
\r
98 except OSError as e:
\r
99 if e.errno == errno.EINTR:
\r
102 if pid not in children:
\r
104 id = children.pop(pid)
\r
105 if os.WIFSIGNALED(status):
\r
106 logger.warning("child %d (pid %d) killed by signal %d, restarting",
\r
107 id, pid, os.WTERMSIG(status))
\r
108 elif os.WEXITSTATUS(status) != 0:
\r
110 "child %d (pid %d) exited with status %d, restarting",
\r
111 id, pid, os.WEXITSTATUS(status))
\r
113 logger.info("child %d (pid %d) exited normally", id, pid)
\r
116 if num_restarts > max_restarts:
\r
117 raise RuntimeError("Too many child restarts, giving up")
\r
118 new_id = start_child(id)
\r
119 if new_id is not None:
\r
121 # All child processes exited cleanly, so exit the master process
\r
122 # instead of just returning to right after the call to
\r
123 # fork_processes (which will probably just start up another IOLoop
\r
124 # unless the caller checks the return value).
\r