X-FTP
[x93.git/.git] / xftp.git / ext / pyftpdlib / prefork.py
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
4 \r
5 """Process utils."""\r
6 \r
7 import errno\r
8 import os\r
9 import sys\r
10 import time\r
11 from binascii import hexlify\r
12 try:\r
13     import multiprocessing\r
14 except ImportError:\r
15     multiprocessing = None\r
16 \r
17 from ._compat import long\r
18 from .log import logger\r
19 \r
20 \r
21 _task_id = None\r
22 \r
23 \r
24 def cpu_count():\r
25     """Returns the number of processors on this machine."""\r
26     if multiprocessing is None:\r
27         return 1\r
28     try:\r
29         return multiprocessing.cpu_count()\r
30     except NotImplementedError:\r
31         pass\r
32     try:\r
33         return os.sysconf("SC_NPROCESSORS_CONF")\r
34     except (AttributeError, ValueError):\r
35         pass\r
36     return 1\r
37 \r
38 \r
39 def _reseed_random():\r
40     if 'random' not in sys.modules:\r
41         return\r
42     import random\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
46     try:\r
47         seed = long(hexlify(os.urandom(16)), 16)\r
48     except NotImplementedError:\r
49         seed = int(time.time() * 1000) ^ os.getpid()\r
50     random.seed(seed)\r
51 \r
52 \r
53 def fork_processes(number, max_restarts=100):\r
54     """Starts multiple worker processes.\r
55 \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
59     sub-processes.\r
60 \r
61     Since we use processes and not threads, there is no shared memory\r
62     between any server code.\r
63 \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
70     """\r
71     global _task_id\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
76     children = {}\r
77 \r
78     def start_child(i):\r
79         pid = os.fork()\r
80         if pid == 0:\r
81             # child process\r
82             _reseed_random()\r
83             global _task_id\r
84             _task_id = i\r
85             return i\r
86         else:\r
87             children[pid] = i\r
88             return None\r
89 \r
90     for i in range(number):\r
91         id = start_child(i)\r
92         if id is not None:\r
93             return id\r
94     num_restarts = 0\r
95     while children:\r
96         try:\r
97             pid, status = os.wait()\r
98         except OSError as e:\r
99             if e.errno == errno.EINTR:\r
100                 continue\r
101             raise\r
102         if pid not in children:\r
103             continue\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
109             logger.warning(\r
110                 "child %d (pid %d) exited with status %d, restarting",\r
111                 id, pid, os.WEXITSTATUS(status))\r
112         else:\r
113             logger.info("child %d (pid %d) exited normally", id, pid)\r
114             continue\r
115         num_restarts += 1\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
120             return new_id\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
125     sys.exit(0)\r