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