|
1 # |
|
2 # Module providing the `Process` class which emulates `threading.Thread` |
|
3 # |
|
4 # multiprocessing/process.py |
|
5 # |
|
6 # Copyright (c) 2006-2008, R Oudkerk --- see COPYING.txt |
|
7 # |
|
8 |
|
9 __all__ = ['Process', 'current_process', 'active_children'] |
|
10 |
|
11 # |
|
12 # Imports |
|
13 # |
|
14 |
|
15 import os |
|
16 import sys |
|
17 import signal |
|
18 import itertools |
|
19 |
|
20 # |
|
21 # |
|
22 # |
|
23 |
|
24 try: |
|
25 ORIGINAL_DIR = os.path.abspath(os.getcwd()) |
|
26 except OSError: |
|
27 ORIGINAL_DIR = None |
|
28 |
|
29 # |
|
30 # Public functions |
|
31 # |
|
32 |
|
33 def current_process(): |
|
34 ''' |
|
35 Return process object representing the current process |
|
36 ''' |
|
37 return _current_process |
|
38 |
|
39 def active_children(): |
|
40 ''' |
|
41 Return list of process objects corresponding to live child processes |
|
42 ''' |
|
43 _cleanup() |
|
44 return list(_current_process._children) |
|
45 |
|
46 # |
|
47 # |
|
48 # |
|
49 |
|
50 def _cleanup(): |
|
51 # check for processes which have finished |
|
52 for p in list(_current_process._children): |
|
53 if p._popen.poll() is not None: |
|
54 _current_process._children.discard(p) |
|
55 |
|
56 # |
|
57 # The `Process` class |
|
58 # |
|
59 |
|
60 class Process(object): |
|
61 ''' |
|
62 Process objects represent activity that is run in a separate process |
|
63 |
|
64 The class is analagous to `threading.Thread` |
|
65 ''' |
|
66 _Popen = None |
|
67 |
|
68 def __init__(self, group=None, target=None, name=None, args=(), kwargs={}): |
|
69 assert group is None, 'group argument must be None for now' |
|
70 count = _current_process._counter.next() |
|
71 self._identity = _current_process._identity + (count,) |
|
72 self._authkey = _current_process._authkey |
|
73 self._daemonic = _current_process._daemonic |
|
74 self._tempdir = _current_process._tempdir |
|
75 self._parent_pid = os.getpid() |
|
76 self._popen = None |
|
77 self._target = target |
|
78 self._args = tuple(args) |
|
79 self._kwargs = dict(kwargs) |
|
80 self._name = name or type(self).__name__ + '-' + \ |
|
81 ':'.join(str(i) for i in self._identity) |
|
82 |
|
83 def run(self): |
|
84 ''' |
|
85 Method to be run in sub-process; can be overridden in sub-class |
|
86 ''' |
|
87 if self._target: |
|
88 self._target(*self._args, **self._kwargs) |
|
89 |
|
90 def start(self): |
|
91 ''' |
|
92 Start child process |
|
93 ''' |
|
94 assert self._popen is None, 'cannot start a process twice' |
|
95 assert self._parent_pid == os.getpid(), \ |
|
96 'can only start a process object created by current process' |
|
97 assert not _current_process._daemonic, \ |
|
98 'daemonic processes are not allowed to have children' |
|
99 _cleanup() |
|
100 if self._Popen is not None: |
|
101 Popen = self._Popen |
|
102 else: |
|
103 from .forking import Popen |
|
104 self._popen = Popen(self) |
|
105 _current_process._children.add(self) |
|
106 |
|
107 def terminate(self): |
|
108 ''' |
|
109 Terminate process; sends SIGTERM signal or uses TerminateProcess() |
|
110 ''' |
|
111 self._popen.terminate() |
|
112 |
|
113 def join(self, timeout=None): |
|
114 ''' |
|
115 Wait until child process terminates |
|
116 ''' |
|
117 assert self._parent_pid == os.getpid(), 'can only join a child process' |
|
118 assert self._popen is not None, 'can only join a started process' |
|
119 res = self._popen.wait(timeout) |
|
120 if res is not None: |
|
121 _current_process._children.discard(self) |
|
122 |
|
123 def is_alive(self): |
|
124 ''' |
|
125 Return whether process is alive |
|
126 ''' |
|
127 if self is _current_process: |
|
128 return True |
|
129 assert self._parent_pid == os.getpid(), 'can only test a child process' |
|
130 if self._popen is None: |
|
131 return False |
|
132 self._popen.poll() |
|
133 return self._popen.returncode is None |
|
134 |
|
135 @property |
|
136 def name(self): |
|
137 return self._name |
|
138 |
|
139 @name.setter |
|
140 def name(self, name): |
|
141 assert isinstance(name, str), 'name must be a string' |
|
142 self._name = name |
|
143 |
|
144 @property |
|
145 def daemon(self): |
|
146 ''' |
|
147 Return whether process is a daemon |
|
148 ''' |
|
149 return self._daemonic |
|
150 |
|
151 @daemon.setter |
|
152 def daemon(self, daemonic): |
|
153 ''' |
|
154 Set whether process is a daemon |
|
155 ''' |
|
156 assert self._popen is None, 'process has already started' |
|
157 self._daemonic = daemonic |
|
158 |
|
159 @property |
|
160 def authkey(self): |
|
161 return self._authkey |
|
162 |
|
163 @authkey.setter |
|
164 def authkey(self, authkey): |
|
165 ''' |
|
166 Set authorization key of process |
|
167 ''' |
|
168 self._authkey = AuthenticationString(authkey) |
|
169 |
|
170 @property |
|
171 def exitcode(self): |
|
172 ''' |
|
173 Return exit code of process or `None` if it has yet to stop |
|
174 ''' |
|
175 if self._popen is None: |
|
176 return self._popen |
|
177 return self._popen.poll() |
|
178 |
|
179 @property |
|
180 def ident(self): |
|
181 ''' |
|
182 Return indentifier (PID) of process or `None` if it has yet to start |
|
183 ''' |
|
184 if self is _current_process: |
|
185 return os.getpid() |
|
186 else: |
|
187 return self._popen and self._popen.pid |
|
188 |
|
189 pid = ident |
|
190 |
|
191 def __repr__(self): |
|
192 if self is _current_process: |
|
193 status = 'started' |
|
194 elif self._parent_pid != os.getpid(): |
|
195 status = 'unknown' |
|
196 elif self._popen is None: |
|
197 status = 'initial' |
|
198 else: |
|
199 if self._popen.poll() is not None: |
|
200 status = self.exitcode |
|
201 else: |
|
202 status = 'started' |
|
203 |
|
204 if type(status) is int: |
|
205 if status == 0: |
|
206 status = 'stopped' |
|
207 else: |
|
208 status = 'stopped[%s]' % _exitcode_to_name.get(status, status) |
|
209 |
|
210 return '<%s(%s, %s%s)>' % (type(self).__name__, self._name, |
|
211 status, self._daemonic and ' daemon' or '') |
|
212 |
|
213 ## |
|
214 |
|
215 def _bootstrap(self): |
|
216 from . import util |
|
217 global _current_process |
|
218 |
|
219 try: |
|
220 self._children = set() |
|
221 self._counter = itertools.count(1) |
|
222 try: |
|
223 os.close(sys.stdin.fileno()) |
|
224 except (OSError, ValueError): |
|
225 pass |
|
226 _current_process = self |
|
227 util._finalizer_registry.clear() |
|
228 util._run_after_forkers() |
|
229 util.info('child process calling self.run()') |
|
230 try: |
|
231 self.run() |
|
232 exitcode = 0 |
|
233 finally: |
|
234 util._exit_function() |
|
235 except SystemExit, e: |
|
236 if not e.args: |
|
237 exitcode = 1 |
|
238 elif type(e.args[0]) is int: |
|
239 exitcode = e.args[0] |
|
240 else: |
|
241 sys.stderr.write(e.args[0] + '\n') |
|
242 sys.stderr.flush() |
|
243 exitcode = 1 |
|
244 except: |
|
245 exitcode = 1 |
|
246 import traceback |
|
247 sys.stderr.write('Process %s:\n' % self.name) |
|
248 sys.stderr.flush() |
|
249 traceback.print_exc() |
|
250 |
|
251 util.info('process exiting with exitcode %d' % exitcode) |
|
252 return exitcode |
|
253 |
|
254 # |
|
255 # We subclass bytes to avoid accidental transmission of auth keys over network |
|
256 # |
|
257 |
|
258 class AuthenticationString(bytes): |
|
259 def __reduce__(self): |
|
260 from .forking import Popen |
|
261 if not Popen.thread_is_spawning(): |
|
262 raise TypeError( |
|
263 'Pickling an AuthenticationString object is ' |
|
264 'disallowed for security reasons' |
|
265 ) |
|
266 return AuthenticationString, (bytes(self),) |
|
267 |
|
268 # |
|
269 # Create object representing the main process |
|
270 # |
|
271 |
|
272 class _MainProcess(Process): |
|
273 |
|
274 def __init__(self): |
|
275 self._identity = () |
|
276 self._daemonic = False |
|
277 self._name = 'MainProcess' |
|
278 self._parent_pid = None |
|
279 self._popen = None |
|
280 self._counter = itertools.count(1) |
|
281 self._children = set() |
|
282 self._authkey = AuthenticationString(os.urandom(32)) |
|
283 self._tempdir = None |
|
284 |
|
285 _current_process = _MainProcess() |
|
286 del _MainProcess |
|
287 |
|
288 # |
|
289 # Give names to some return codes |
|
290 # |
|
291 |
|
292 _exitcode_to_name = {} |
|
293 |
|
294 for name, signum in signal.__dict__.items(): |
|
295 if name[:3]=='SIG' and '_' not in name: |
|
296 _exitcode_to_name[-signum] = name |