]>
Commit | Line | Data |
---|---|---|
1 | #!/usr/bin/env python | |
2 | # Copyright (c) 2002-2003 ActiveState | |
3 | # See LICENSE.txt for license details. | |
4 | """ Contents of LICENSE.txt: | |
5 | Permission is hereby granted, free of charge, to any person obtaining a | |
6 | copy of this software and associated documentation files (the | |
7 | "Software"), to deal in the Software without restriction, including | |
8 | without limitation the rights to use, copy, modify, merge, publish, | |
9 | distribute, sublicense, and/or sell copies of the Software, and to | |
10 | permit persons to whom the Software is furnished to do so, subject to | |
11 | the following conditions: | |
12 | ||
13 | The above copyright notice and this permission notice shall be included | |
14 | in all copies or substantial portions of the Software. | |
15 | ||
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | |
17 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |
20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
23 | """ | |
24 | ||
25 | r""" | |
26 | Python interface for process control. | |
27 | ||
28 | This module defines three Process classes for spawning, | |
29 | communicating and control processes. They are: Process, ProcessOpen, | |
30 | ProcessProxy. All of the classes allow one to specify the command (cmd), | |
31 | starting working directory (cwd), and environment to create for the | |
32 | new process (env) and to "wait" for termination of the child and | |
33 | "kill" the child. | |
34 | ||
35 | Process: | |
36 | Use this class to simply launch a process (either a GUI app or a | |
37 | console app in a new console) with which you do not intend to | |
38 | communicate via it std handles. | |
39 | ||
40 | ProcessOpen: | |
41 | Think of this as a super version of Python's os.popen3() method. | |
42 | This spawns the given command and sets up pipes for | |
43 | stdin/stdout/stderr which can then be used to communicate with | |
44 | the child. | |
45 | ||
46 | ProcessProxy: | |
47 | This is a heavy-weight class that, similar to ProcessOpen, | |
48 | spawns the given commands and sets up pipes to the child's | |
49 | stdin/stdout/stderr. However, it also starts three threads to | |
50 | proxy communication between each of the child's and parent's std | |
51 | handles. At the parent end of this communication are, by | |
52 | default, IOBuffer objects. You may specify your own objects here | |
53 | (usually sub-classing from IOBuffer, which handles some | |
54 | synchronization issues for you). The result is that it is | |
55 | possible to have your own IOBuffer instance that gets, say, a | |
56 | .write() "event" for every write that the child does on its | |
57 | stdout. | |
58 | ||
59 | Understanding ProcessProxy is pretty complex. Some examples | |
60 | below attempt to help show some uses. Here is a diagram of the | |
61 | comminucation: | |
62 | ||
63 | <parent process> | |
64 | ,---->->->------' ^ `------>->->----, | |
65 | | | v | |
66 | IOBuffer IOBuffer IOBuffer | |
67 | (p.stdout) (p.stderr) (p.stdin) | |
68 | | | | | |
69 | _OutFileProxy _OutFileProxy _InFileProxy | |
70 | thread thread thread | |
71 | | ^ | | |
72 | `----<-<-<------, | ,------<-<-<----' | |
73 | <child process> | |
74 | ||
75 | Usage: | |
76 | import process | |
77 | p = process.<Process class>(cmd='echo hi', ...) | |
78 | #... use the various methods and attributes | |
79 | ||
80 | Examples: | |
81 | A simple 'hello world': | |
82 | >>> import process | |
83 | >>> p = process.ProcessOpen(['echo', 'hello']) | |
84 | >>> p.stdout.read() | |
85 | 'hello\r\n' | |
86 | >>> p.wait() # .wait() returns the child's exit status | |
87 | 0 | |
88 | ||
89 | Redirecting the stdout handler: | |
90 | >>> import sys | |
91 | >>> p = process.ProcessProxy(['echo', 'hello'], stdout=sys.stdout) | |
92 | hello | |
93 | ||
94 | Using stdin (need to use ProcessProxy here because it defaults to | |
95 | text-mode translation on Windows, ProcessOpen does not support | |
96 | this): | |
97 | >>> p = process.ProcessProxy(['sort']) | |
98 | >>> p.stdin.write('5\n') | |
99 | >>> p.stdin.write('2\n') | |
100 | >>> p.stdin.write('7\n') | |
101 | >>> p.stdin.close() | |
102 | >>> p.stdout.read() | |
103 | '2\n5\n7\n' | |
104 | ||
105 | Specifying environment variables: | |
106 | >>> p = process.ProcessOpen(['perl', '-e', 'print $ENV{FOO}']) | |
107 | >>> p.stdout.read() | |
108 | '' | |
109 | >>> p = process.ProcessOpen(['perl', '-e', 'print $ENV{FOO}'], | |
110 | ... env={'FOO':'bar'}) | |
111 | >>> p.stdout.read() | |
112 | 'bar' | |
113 | ||
114 | Killing a long running process (On Linux, to poll you must use | |
115 | p.wait(os.WNOHANG)): | |
116 | >>> p = ProcessOpen(['perl', '-e', 'while (1) {}']) | |
117 | >>> try: | |
118 | ... p.wait(os.WNOHANG) # poll to see if is process still running | |
119 | ... except ProcessError, ex: | |
120 | ... if ex.errno == ProcessProxy.WAIT_TIMEOUT: | |
121 | ... print "process is still running" | |
122 | ... | |
123 | process is still running | |
124 | >>> p.kill(42) | |
125 | >>> p.wait() | |
126 | 42 | |
127 | ||
128 | Providing objects for stdin/stdout/stderr: | |
129 | XXX write this, mention IOBuffer subclassing. | |
130 | """ | |
131 | #TODO: | |
132 | # - Discuss the decision to NOT have the stdout/stderr _OutFileProxy's | |
133 | # wait for process termination before closing stdin. It will just | |
134 | # close stdin when stdout is seen to have been closed. That is | |
135 | # considered Good Enough (tm). Theoretically it would be nice to | |
136 | # only abort the stdin proxying when the process terminates, but | |
137 | # watching for process termination in any of the parent's thread | |
138 | # adds the undesired condition that the parent cannot exit with the | |
139 | # child still running. That sucks. | |
140 | # XXX Note that I don't even know if the current stdout proxy even | |
141 | # closes the stdin proxy at all. | |
142 | # - DavidA: if I specify "unbuffered" for my stdin handler (in the | |
143 | # ProcessProxy constructor) then the stdin IOBuffer should do a | |
144 | # fparent.read() rather than a fparent.readline(). TrentM: can I do | |
145 | # that? What happens? | |
146 | # | |
147 | ||
148 | import os | |
149 | import sys | |
150 | import threading | |
151 | import types | |
152 | import pprint | |
153 | if sys.platform.startswith("win"): | |
154 | import msvcrt | |
155 | import win32api | |
156 | import win32file | |
157 | import win32pipe | |
158 | import pywintypes | |
159 | import win32process | |
160 | import win32event | |
161 | # constants pulled from win32con to save memory | |
162 | VER_PLATFORM_WIN32_WINDOWS = 1 | |
163 | CTRL_BREAK_EVENT = 1 | |
164 | SW_SHOWDEFAULT = 10 | |
165 | WM_CLOSE = 0x10 | |
166 | DUPLICATE_SAME_ACCESS = 2 | |
167 | ||
168 | else: | |
169 | import signal | |
170 | ||
171 | ||
172 | #---- exceptions | |
173 | ||
174 | class ProcessError(Exception): | |
175 | def __init__(self, msg, errno=-1): | |
176 | Exception.__init__(self, msg) | |
177 | self.errno = errno | |
178 | ||
179 | ||
180 | #---- internal logging facility | |
181 | ||
182 | class Logger: | |
183 | DEBUG, INFO, WARN, ERROR, FATAL = range(5) | |
184 | def __init__(self, name, level=None, streamOrFileName=sys.stderr): | |
185 | self.name = name | |
186 | if level is None: | |
187 | self.level = self.WARN | |
188 | else: | |
189 | self.level = level | |
190 | if type(streamOrFileName) == types.StringType: | |
191 | self.stream = open(streamOrFileName, 'w') | |
192 | self._opennedStream = 1 | |
193 | else: | |
194 | self.stream = streamOrFileName | |
195 | self._opennedStream = 0 | |
196 | def __del__(self): | |
197 | if self._opennedStream: | |
198 | self.stream.close() | |
199 | def _getLevelName(self, level): | |
200 | levelNameMap = { | |
201 | self.DEBUG: "DEBUG", | |
202 | self.INFO: "INFO", | |
203 | self.WARN: "WARN", | |
204 | self.ERROR: "ERROR", | |
205 | self.FATAL: "FATAL", | |
206 | } | |
207 | return levelNameMap[level] | |
208 | def log(self, level, msg, *args): | |
209 | if level < self.level: | |
210 | return | |
211 | message = "%s: %s:" % (self.name, self._getLevelName(level).lower()) | |
212 | message = message + (msg % args) + "\n" | |
213 | self.stream.write(message) | |
214 | self.stream.flush() | |
215 | def debug(self, msg, *args): | |
216 | self.log(self.DEBUG, msg, *args) | |
217 | def info(self, msg, *args): | |
218 | self.log(self.INFO, msg, *args) | |
219 | def warn(self, msg, *args): | |
220 | self.log(self.WARN, msg, *args) | |
221 | def error(self, msg, *args): | |
222 | self.log(self.ERROR, msg, *args) | |
223 | def fatal(self, msg, *args): | |
224 | self.log(self.FATAL, msg, *args) | |
225 | ||
226 | # Loggers: | |
227 | # - 'log' to log normal process handling | |
228 | # - 'logres' to track system resource life | |
229 | # - 'logfix' to track wait/kill proxying in _ThreadFixer | |
230 | if 1: # normal/production usage | |
231 | log = Logger("process", Logger.WARN) | |
232 | else: # development/debugging usage | |
233 | log = Logger("process", Logger.DEBUG, sys.stdout) | |
234 | if 1: # normal/production usage | |
235 | logres = Logger("process.res", Logger.WARN) | |
236 | else: # development/debugging usage | |
237 | logres = Logger("process.res", Logger.DEBUG, sys.stdout) | |
238 | if 1: # normal/production usage | |
239 | logfix = Logger("process.waitfix", Logger.WARN) | |
240 | else: # development/debugging usage | |
241 | logfix = Logger("process.waitfix", Logger.DEBUG, sys.stdout) | |
242 | ||
243 | ||
244 | ||
245 | #---- globals | |
246 | ||
247 | _version_ = (0, 5, 0) | |
248 | ||
249 | # List of registered processes (see _(un)registerProcess). | |
250 | _processes = [] | |
251 | ||
252 | ||
253 | ||
254 | #---- internal support routines | |
255 | ||
256 | def _escapeArg(arg): | |
257 | """Escape the given command line argument for the shell.""" | |
258 | #XXX There is a probably more that we should escape here. | |
259 | return arg.replace('"', r'\"') | |
260 | ||
261 | ||
262 | def _joinArgv(argv): | |
263 | r"""Join an arglist to a string appropriate for running. | |
264 | ||
265 | >>> import os | |
266 | >>> _joinArgv(['foo', 'bar "baz']) | |
267 | 'foo "bar \\"baz"' | |
268 | """ | |
269 | cmdstr = "" | |
270 | for arg in argv: | |
271 | if ' ' in arg or ';' in arg: | |
272 | cmdstr += '"%s"' % _escapeArg(arg) | |
273 | else: | |
274 | cmdstr += _escapeArg(arg) | |
275 | cmdstr += ' ' | |
276 | if cmdstr.endswith(' '): cmdstr = cmdstr[:-1] # strip trailing space | |
277 | return cmdstr | |
278 | ||
279 | ||
280 | def _getPathFromEnv(env): | |
281 | """Return the PATH environment variable or None. | |
282 | ||
283 | Do the right thing for case sensitivity per platform. | |
284 | XXX Icky. This guarantee of proper case sensitivity of environment | |
285 | variables should be done more fundamentally in this module. | |
286 | """ | |
287 | if sys.platform.startswith("win"): | |
288 | for key in env.keys(): | |
289 | if key.upper() == "PATH": | |
290 | return env[key] | |
291 | else: | |
292 | return None | |
293 | else: | |
294 | if env.has_key("PATH"): | |
295 | return env["PATH"] | |
296 | else: | |
297 | return None | |
298 | ||
299 | ||
300 | def _whichFirstArg(cmd, env=None): | |
301 | """Return the given command ensuring that the first arg (the command to | |
302 | launch) is a full path to an existing file. | |
303 | ||
304 | Raise a ProcessError if no such executable could be found. | |
305 | """ | |
306 | # Parse out the first arg. | |
307 | if cmd.startswith('"'): | |
308 | # The .replace() is to ensure it does not mistakenly find the | |
309 | # second '"' in, say (escaped quote): | |
310 | # "C:\foo\"bar" arg1 arg2 | |
311 | idx = cmd.replace('\\"', 'XX').find('"', 1) | |
312 | if idx == -1: | |
313 | raise ProcessError("Malformed command: %r" % cmd) | |
314 | first, rest = cmd[1:idx], cmd[idx+1:] | |
315 | rest = rest.lstrip() | |
316 | else: | |
317 | if ' ' in cmd: | |
318 | first, rest = cmd.split(' ', 1) | |
319 | else: | |
320 | first, rest = cmd, "" | |
321 | ||
322 | # Ensure the first arg is a valid path to the appropriate file. | |
323 | import which | |
324 | if os.sep in first: | |
325 | altpath = [os.path.dirname(first)] | |
326 | firstbase = os.path.basename(first) | |
327 | candidates = list(which.which(firstbase, path=altpath)) | |
328 | elif env: | |
329 | altpath = _getPathFromEnv(env) | |
330 | if altpath: | |
331 | candidates = list(which.which(first, altpath.split(os.pathsep))) | |
332 | else: | |
333 | candidates = list(which.which(first)) | |
334 | else: | |
335 | candidates = list(which.which(first)) | |
336 | if candidates: | |
337 | return _joinArgv( [candidates[0]] ) + ' ' + rest | |
338 | else: | |
339 | raise ProcessError("Could not find an appropriate leading command "\ | |
340 | "for: %r" % cmd) | |
341 | ||
342 | ||
343 | if sys.platform.startswith("win"): | |
344 | def _SaferCreateProcess(appName, # app name | |
345 | cmd, # command line | |
346 | processSA, # process security attributes | |
347 | threadSA, # thread security attributes | |
348 | inheritHandles, # are handles are inherited | |
349 | creationFlags, # creation flags | |
350 | env, # environment | |
351 | cwd, # current working directory | |
352 | si): # STARTUPINFO pointer | |
353 | """If CreateProcess fails from environment type inconsistency then | |
354 | fix that and try again. | |
355 | ||
356 | win32process.CreateProcess requires that all environment keys and | |
357 | values either be all ASCII or all unicode. Try to remove this burden | |
358 | from the user of process.py. | |
359 | """ | |
360 | isWin9x = win32api.GetVersionEx()[3] == VER_PLATFORM_WIN32_WINDOWS | |
361 | # On Win9x all keys and values of 'env' must be ASCII (XXX | |
362 | # Actually this is probably only true if the Unicode support | |
363 | # libraries, which are not installed by default, are not | |
364 | # installed). On other Windows flavours all keys and values of | |
365 | # 'env' must all be ASCII *or* all Unicode. We will try to | |
366 | # automatically convert to the appropriate type, issuing a | |
367 | # warning if such an automatic conversion is necessary. | |
368 | ||
369 | #XXX Komodo 2.0 Beta 1 hack. This requirement should be | |
370 | # pushed out to Komodo code using process.py. Or should it? | |
371 | if isWin9x and env: | |
372 | aenv = {} | |
373 | for key, value in env.items(): | |
374 | aenv[str(key)] = str(value) | |
375 | env = aenv | |
376 | ||
377 | log.debug("""\ | |
378 | _SaferCreateProcess(appName=%r, | |
379 | cmd=%r, | |
380 | env=%r, | |
381 | cwd=%r) | |
382 | os.getcwd(): %r | |
383 | """, appName, cmd, env, cwd, os.getcwd()) | |
384 | try: | |
385 | hProcess, hThread, processId, threadId\ | |
386 | = win32process.CreateProcess(appName, cmd, processSA, | |
387 | threadSA, inheritHandles, | |
388 | creationFlags, env, cwd, si) | |
389 | except TypeError, ex: | |
390 | if ex.args == ('All dictionary items must be strings, or all must be unicode',): | |
391 | # Try again with an all unicode environment. | |
392 | #XXX Would be nice if didn't have to depend on the error | |
393 | # string to catch this. | |
394 | #XXX Removing this warning for 2.3 release. See bug | |
395 | # 23215. The right fix is to correct the PHPAppInfo | |
396 | # stuff to heed the warning. | |
397 | #import warnings | |
398 | #warnings.warn('env: ' + str(ex), stacklevel=4) | |
399 | if isWin9x and env: | |
400 | aenv = {} | |
401 | try: | |
402 | for key, value in env.items(): | |
403 | aenv[str(key)] = str(value) | |
404 | except UnicodeError, ex: | |
405 | raise ProcessError(str(ex)) | |
406 | env = aenv | |
407 | elif env: | |
408 | uenv = {} | |
409 | for key, val in env.items(): | |
410 | uenv[unicode(key)] = unicode(val) | |
411 | env = uenv | |
412 | hProcess, hThread, processId, threadId\ | |
413 | = win32process.CreateProcess(appName, cmd, processSA, | |
414 | threadSA, inheritHandles, | |
415 | creationFlags, env, cwd, | |
416 | si) | |
417 | else: | |
418 | raise | |
419 | return hProcess, hThread, processId, threadId | |
420 | ||
421 | ||
422 | # Maintain references to all spawned ProcessProxy objects to avoid hangs. | |
423 | # Otherwise, if the user lets the a ProcessProxy object go out of | |
424 | # scope before the process has terminated, it is possible to get a | |
425 | # hang (at least it *used* to be so when we had the | |
426 | # win32api.CloseHandle(<stdin handle>) call in the __del__() method). | |
427 | # XXX Is this hang possible on Linux as well? | |
428 | # A reference is removed from this list when the process's .wait or | |
429 | # .kill method is called. | |
430 | # XXX Should an atexit() handler be registered to kill all curently | |
431 | # running processes? Else *could* get hangs, n'est ce pas? | |
432 | def _registerProcess(process): | |
433 | global _processes | |
434 | log.info("_registerprocess(process=%r)", process) | |
435 | ||
436 | # Clean up zombie processes. | |
437 | # If the user does not call .wait() or .kill() on processes then | |
438 | # the ProcessProxy object will not get cleaned up until Python | |
439 | # exits and _processes goes out of scope. Under heavy usage that | |
440 | # is a big memory waste. Cleaning up here alleviates that. | |
441 | for p in _processes[:]: # use copy of _process, because we may modifiy it | |
442 | try: | |
443 | # poll to see if is process still running | |
444 | if sys.platform.startswith("win"): | |
445 | timeout = 0 | |
446 | else: | |
447 | timeout = os.WNOHANG | |
448 | p.wait(timeout) | |
449 | _unregisterProcess(p) | |
450 | except ProcessError, ex: | |
451 | if ex.errno == ProcessProxy.WAIT_TIMEOUT: | |
452 | pass | |
453 | else: | |
454 | raise | |
455 | ||
456 | _processes.append(process) | |
457 | ||
458 | def _unregisterProcess(process): | |
459 | global _processes | |
460 | log.info("_unregisterProcess(process=%r)", process) | |
461 | try: | |
462 | _processes.remove(process) | |
463 | del process | |
464 | except ValueError: | |
465 | pass | |
466 | ||
467 | ||
468 | def _fixupCommand(cmd, env=None): | |
469 | """Fixup the command string so it is launchable via CreateProcess. | |
470 | ||
471 | One cannot just launch, say "python", via CreateProcess. A full path | |
472 | to an executable is required. In general there are two choices: | |
473 | 1. Launch the command string via the shell. The shell will find | |
474 | the fullpath to the appropriate executable. This shell will | |
475 | also be able to execute special shell commands, like "dir", | |
476 | which don't map to an actual executable. | |
477 | 2. Find the fullpath to the appropriate executable manually and | |
478 | launch that exe. | |
479 | ||
480 | Option (1) is preferred because you don't have to worry about not | |
481 | exactly duplicating shell behaviour and you get the added bonus of | |
482 | being able to launch "dir" and friends. | |
483 | ||
484 | However, (1) is not always an option. Doing so when the shell is | |
485 | command.com (as on all Win9x boxes) or when using WinNT's cmd.exe, | |
486 | problems are created with .kill() because these shells seem to eat | |
487 | up Ctrl-C's and Ctrl-Break's sent via | |
488 | win32api.GenerateConsoleCtrlEvent(). Strangely this only happens | |
489 | when spawn via this Python interface. For example, Ctrl-C get | |
490 | through to hang.exe here: | |
491 | C:\> ...\w9xpopen.exe "C:\WINDOWS\COMMAND.COM /c hang.exe" | |
492 | ^C | |
493 | but not here: | |
494 | >>> p = ProcessOpen('hang.exe') | |
495 | # This results in the same command to CreateProcess as | |
496 | # above. | |
497 | >>> p.kill() | |
498 | ||
499 | Hence, for these platforms we fallback to option (2). Cons: | |
500 | - cannot spawn shell commands like 'dir' directly | |
501 | - cannot spawn batch files | |
502 | """ | |
503 | if sys.platform.startswith("win"): | |
504 | # Fixup the command string to spawn. (Lifted from | |
505 | # posixmodule.c::_PyPopenCreateProcess() with some modifications) | |
506 | comspec = os.environ.get("COMSPEC", None) | |
507 | win32Version = win32api.GetVersion() | |
508 | if comspec is None: | |
509 | raise ProcessError("Cannot locate a COMSPEC environment "\ | |
510 | "variable to use as the shell") | |
511 | # Explicitly check if we are using COMMAND.COM. If we | |
512 | # are then use the w9xpopen hack. | |
513 | elif (win32Version & 0x80000000L == 0) and\ | |
514 | (win32Version & 0x5L >= 5) and\ | |
515 | os.path.basename(comspec).lower() != "command.com": | |
516 | # 2000/XP and not using command.com. | |
517 | if '"' in cmd or "'" in cmd: | |
518 | cmd = comspec + ' /c "%s"' % cmd | |
519 | else: | |
520 | cmd = comspec + ' /c ' + cmd | |
521 | elif (win32Version & 0x80000000L == 0) and\ | |
522 | (win32Version & 0x5L < 5) and\ | |
523 | os.path.basename(comspec).lower() != "command.com": | |
524 | # NT and not using command.com. | |
525 | try: | |
526 | cmd = _whichFirstArg(cmd, env) | |
527 | except ProcessError: | |
528 | raise ProcessError("Could not find a suitable executable "\ | |
529 | "to launch for '%s'. On WinNT you must manually prefix "\ | |
530 | "shell commands and batch files with 'cmd.exe /c' to "\ | |
531 | "have the shell run them." % cmd) | |
532 | else: | |
533 | # Oh gag, we're on Win9x and/or using COMMAND.COM. Use the | |
534 | # workaround listed in KB: Q150956 | |
535 | w9xpopen = os.path.join( | |
536 | os.path.dirname(win32api.GetModuleFileName(0)), | |
537 | 'w9xpopen.exe') | |
538 | if not os.path.exists(w9xpopen): | |
539 | # Eeek - file-not-found - possibly an embedding | |
540 | # situation - see if we can locate it in sys.exec_prefix | |
541 | w9xpopen = os.path.join(os.path.dirname(sys.exec_prefix), | |
542 | 'w9xpopen.exe') | |
543 | if not os.path.exists(w9xpopen): | |
544 | raise ProcessError(\ | |
545 | "Can not locate 'w9xpopen.exe' which is needed "\ | |
546 | "for ProcessOpen to work with your shell or "\ | |
547 | "platform.") | |
548 | ## This would be option (1): | |
549 | #cmd = '%s "%s /c %s"'\ | |
550 | # % (w9xpopen, comspec, cmd.replace('"', '\\"')) | |
551 | try: | |
552 | cmd = _whichFirstArg(cmd, env) | |
553 | except ProcessError: | |
554 | raise ProcessError("Could not find a suitable executable "\ | |
555 | "to launch for '%s'. On Win9x you must manually prefix "\ | |
556 | "shell commands and batch files with 'command.com /c' "\ | |
557 | "to have the shell run them." % cmd) | |
558 | cmd = '%s "%s"' % (w9xpopen, cmd.replace('"', '\\"')) | |
559 | return cmd | |
560 | ||
561 | class _FileWrapper: | |
562 | """Wrap a system file object, hiding some nitpicky details. | |
563 | ||
564 | This class provides a Python file-like interface to either a Python | |
565 | file object (pretty easy job), a file descriptor, or an OS-specific | |
566 | file handle (e.g. Win32 handles to file objects on Windows). Any or | |
567 | all of these object types may be passed to this wrapper. If more | |
568 | than one is specified this wrapper prefers to work with certain one | |
569 | in this order: | |
570 | - file descriptor (because usually this allows for | |
571 | return-immediately-on-read-if-anything-available semantics and | |
572 | also provides text mode translation on Windows) | |
573 | - OS-specific handle (allows for the above read semantics) | |
574 | - file object (buffering can cause difficulty for interacting | |
575 | with spawned programs) | |
576 | ||
577 | It also provides a place where related such objects can be kept | |
578 | alive together to prevent premature ref-counted collection. (E.g. on | |
579 | Windows a Python file object may be associated with a Win32 file | |
580 | handle. If the file handle is not kept alive the Python file object | |
581 | will cease to function.) | |
582 | """ | |
583 | def __init__(self, file=None, descriptor=None, handle=None): | |
584 | self._file = file | |
585 | self._descriptor = descriptor | |
586 | self._handle = handle | |
587 | self._closed = 0 | |
588 | if self._descriptor is not None or self._handle is not None: | |
589 | self._lineBuf = "" # to support .readline() | |
590 | ||
591 | def __del__(self): | |
592 | self.close() | |
593 | ||
594 | def __getattr__(self, name): | |
595 | """Forward to the underlying file object.""" | |
596 | if self._file is not None: | |
597 | return getattr(self._file, name) | |
598 | else: | |
599 | raise ProcessError("no file object to pass '%s' attribute to" | |
600 | % name) | |
601 | ||
602 | def _win32Read(self, nBytes): | |
603 | try: | |
604 | log.info("[%s] _FileWrapper.read: waiting for read on pipe", | |
605 | id(self)) | |
606 | errCode, text = win32file.ReadFile(self._handle, nBytes) | |
607 | except pywintypes.error, ex: | |
608 | # Ignore errors for now, like "The pipe is being closed.", | |
609 | # etc. XXX There *may* be errors we don't want to avoid. | |
610 | log.info("[%s] _FileWrapper.read: error reading from pipe: %s", | |
611 | id(self), ex) | |
612 | return "" | |
613 | assert errCode == 0,\ | |
614 | "Why is 'errCode' from ReadFile non-zero? %r" % errCode | |
615 | if not text: | |
616 | # Empty text signifies that the pipe has been closed on | |
617 | # the parent's end. | |
618 | log.info("[%s] _FileWrapper.read: observed close of parent", | |
619 | id(self)) | |
620 | # Signal the child so it knows to stop listening. | |
621 | self.close() | |
622 | return "" | |
623 | else: | |
624 | log.info("[%s] _FileWrapper.read: read %d bytes from pipe: %r", | |
625 | id(self), len(text), text) | |
626 | return text | |
627 | ||
628 | def read(self, nBytes=-1): | |
629 | # nBytes <= 0 means "read everything" | |
630 | # Note that we are changing the "read everything" cue to | |
631 | # include 0, because actually doing | |
632 | # win32file.ReadFile(<handle>, 0) results in every subsequent | |
633 | # read returning 0, i.e. it shuts down the pipe. | |
634 | if self._descriptor is not None: | |
635 | if nBytes <= 0: | |
636 | text, self._lineBuf = self._lineBuf, "" | |
637 | while 1: | |
638 | t = os.read(self._descriptor, 4092) | |
639 | if not t: | |
640 | break | |
641 | else: | |
642 | text += t | |
643 | else: | |
644 | if len(self._lineBuf) >= nBytes: | |
645 | text, self._lineBuf =\ | |
646 | self._lineBuf[:nBytes], self._lineBuf[nBytes:] | |
647 | else: | |
648 | nBytesToGo = nBytes - len(self._lineBuf) | |
649 | text = self._lineBuf + os.read(self._descriptor, | |
650 | nBytesToGo) | |
651 | self._lineBuf = "" | |
652 | return text | |
653 | elif self._handle is not None: | |
654 | if nBytes <= 0: | |
655 | text, self._lineBuf = self._lineBuf, "" | |
656 | while 1: | |
657 | t = self._win32Read(4092) | |
658 | if not t: | |
659 | break | |
660 | else: | |
661 | text += t | |
662 | else: | |
663 | if len(self._lineBuf) >= nBytes: | |
664 | text, self._lineBuf =\ | |
665 | self._lineBuf[:nBytes], self._lineBuf[nBytes:] | |
666 | else: | |
667 | nBytesToGo = nBytes - len(self._lineBuf) | |
668 | text, self._lineBuf =\ | |
669 | self._lineBuf + self._win32Read(nBytesToGo), "" | |
670 | return text | |
671 | elif self._file is not None: | |
672 | return self._file.read(nBytes) | |
673 | else: | |
674 | raise "FileHandle.read: no handle to read with" | |
675 | ||
676 | def readline(self): | |
677 | if self._descriptor is not None or self._handle is not None: | |
678 | while 1: | |
679 | #XXX This is not portable to the Mac. | |
680 | idx = self._lineBuf.find('\n') | |
681 | if idx != -1: | |
682 | line, self._lineBuf =\ | |
683 | self._lineBuf[:idx+1], self._lineBuf[idx+1:] | |
684 | break | |
685 | else: | |
686 | lengthBefore = len(self._lineBuf) | |
687 | t = self.read(4092) | |
688 | if len(t) <= lengthBefore: # no new data was read | |
689 | line, self._lineBuf = self._lineBuf, "" | |
690 | break | |
691 | else: | |
692 | self._lineBuf += t | |
693 | return line | |
694 | elif self._file is not None: | |
695 | return self._file.readline() | |
696 | else: | |
697 | raise "FileHandle.readline: no handle to read with" | |
698 | ||
699 | def readlines(self): | |
700 | if self._descriptor is not None or self._handle is not None: | |
701 | lines = [] | |
702 | while 1: | |
703 | line = self.readline() | |
704 | if line: | |
705 | lines.append(line) | |
706 | else: | |
707 | break | |
708 | return lines | |
709 | elif self._file is not None: | |
710 | return self._file.readlines() | |
711 | else: | |
712 | raise "FileHandle.readline: no handle to read with" | |
713 | ||
714 | def write(self, text): | |
715 | if self._descriptor is not None: | |
716 | os.write(self._descriptor, text) | |
717 | elif self._handle is not None: | |
718 | try: | |
719 | errCode, nBytesWritten = win32file.WriteFile(self._handle, text) | |
720 | except pywintypes.error, ex: | |
721 | # Ingore errors like "The pipe is being closed.", for | |
722 | # now. | |
723 | log.info("[%s] _FileWrapper.write: error writing to pipe, "\ | |
724 | "ignored", id(self)) | |
725 | return | |
726 | assert errCode == 0,\ | |
727 | "Why is 'errCode' from WriteFile non-zero? %r" % errCode | |
728 | if not nBytesWritten: | |
729 | # No bytes written signifies that the pipe has been | |
730 | # closed on the child's end. | |
731 | log.info("[%s] _FileWrapper.write: observed close of pipe", | |
732 | id(self)) | |
733 | return | |
734 | else: | |
735 | log.info("[%s] _FileWrapper.write: wrote %d bytes to pipe: %r", | |
736 | id(self), len(text), text) | |
737 | elif self._file is not None: | |
738 | self._file.write(text) | |
739 | else: | |
740 | raise "FileHandle.write: nothing to write with" | |
741 | ||
742 | def close(self): | |
743 | """Close all associated file objects and handles.""" | |
744 | log.debug("[%s] _FileWrapper.close()", id(self)) | |
745 | if not self._closed: | |
746 | self._closed = 1 | |
747 | if self._file is not None: | |
748 | log.debug("[%s] _FileWrapper.close: close file", id(self)) | |
749 | self._file.close() | |
750 | log.debug("[%s] _FileWrapper.close: done file close", id(self)) | |
751 | if self._descriptor is not None: | |
752 | try: | |
753 | os.close(self._descriptor) | |
754 | except OSError, ex: | |
755 | if ex.errno == 9: | |
756 | # Ignore: OSError: [Errno 9] Bad file descriptor | |
757 | # XXX *Should* we be ignoring this? It appears very | |
758 | # *in*frequently in test_wait.py. | |
759 | log.debug("[%s] _FileWrapper.close: closing "\ | |
760 | "descriptor raised OSError", id(self)) | |
761 | else: | |
762 | raise | |
763 | if self._handle is not None: | |
764 | log.debug("[%s] _FileWrapper.close: close handle", id(self)) | |
765 | try: | |
766 | win32api.CloseHandle(self._handle) | |
767 | except win32api.error: | |
768 | log.debug("[%s] _FileWrapper.close: closing handle raised", | |
769 | id(self)) | |
770 | pass | |
771 | log.debug("[%s] _FileWrapper.close: done closing handle", | |
772 | id(self)) | |
773 | ||
774 | def __repr__(self): | |
775 | return "<_FileWrapper: file:%r fd:%r os_handle:%r>"\ | |
776 | % (self._file, self._descriptor, self._handle) | |
777 | ||
778 | ||
779 | class _CountingCloser: | |
780 | """Call .close() on the given object after own .close() is called | |
781 | the precribed number of times. | |
782 | """ | |
783 | def __init__(self, objectsToClose, count): | |
784 | """ | |
785 | "objectsToClose" is a list of object on which to call .close(). | |
786 | "count" is the number of times this object's .close() method | |
787 | must be called before .close() is called on the given objects. | |
788 | """ | |
789 | self.objectsToClose = objectsToClose | |
790 | self.count = count | |
791 | if self.count <= 0: | |
792 | raise ProcessError("illegal 'count' value: %s" % self.count) | |
793 | ||
794 | def close(self): | |
795 | self.count -= 1 | |
796 | log.debug("[%d] _CountingCloser.close(): count=%d", id(self), | |
797 | self.count) | |
798 | if self.count == 0: | |
799 | for objectToClose in self.objectsToClose: | |
800 | objectToClose.close() | |
801 | ||
802 | ||
803 | ||
804 | #---- public interface | |
805 | ||
806 | class Process: | |
807 | """Create a process. | |
808 | ||
809 | One can optionally specify the starting working directory, the | |
810 | process environment, and std handles to have the child process | |
811 | inherit (all defaults are the parent's current settings). 'wait' and | |
812 | 'kill' method allow for control of the child's termination. | |
813 | """ | |
814 | # TODO: | |
815 | # - Rename this or merge it with ProcessOpen somehow. | |
816 | # | |
817 | if sys.platform.startswith("win"): | |
818 | # .wait() argument constants | |
819 | INFINITE = win32event.INFINITE | |
820 | # .wait() return error codes | |
821 | WAIT_FAILED = win32event.WAIT_FAILED | |
822 | WAIT_TIMEOUT = win32event.WAIT_TIMEOUT | |
823 | # creation "flags" constants | |
824 | # XXX Should drop these and just document usage of | |
825 | # win32process.CREATE_* constants on windows. | |
826 | CREATE_NEW_CONSOLE = win32process.CREATE_NEW_CONSOLE | |
827 | else: | |
828 | # .wait() argument constants | |
829 | INFINITE = 0 | |
830 | # .wait() return error codes | |
831 | WAIT_TIMEOUT = 258 | |
832 | WAIT_FAILED = -1 | |
833 | # creation "flags" constants | |
834 | CREATE_NEW_CONSOLE = 0x10 # same as win32process.CREATE_NEW_CONSOLE | |
835 | ||
836 | def __init__(self, cmd, cwd=None, env=None, flags=0): | |
837 | """Create a child process. | |
838 | ||
839 | "cmd" is a command string or argument vector to spawn. | |
840 | "cwd" is a working directory in which to start the child process. | |
841 | "env" is an environment dictionary for the child. | |
842 | "flags" are system-specific process creation flags. On Windows | |
843 | this can be a bitwise-OR of any of the win32process.CREATE_* | |
844 | constants (Note: win32process.CREATE_NEW_PROCESS_GROUP is always | |
845 | OR'd in). On Unix, this is currently ignored. | |
846 | """ | |
847 | log.info("Process.__init__(cmd=%r, cwd=%r, env=%r, flags=%r)", | |
848 | cmd, cwd, env, flags) | |
849 | self._cmd = cmd | |
850 | if not self._cmd: | |
851 | raise ProcessError("You must specify a command.") | |
852 | self._cwd = cwd | |
853 | self._env = env | |
854 | self._flags = flags | |
855 | if sys.platform.startswith("win"): | |
856 | self._flags |= win32process.CREATE_NEW_PROCESS_GROUP | |
857 | ||
858 | if sys.platform.startswith("win"): | |
859 | self._startOnWindows() | |
860 | else: | |
861 | self.__retvalCache = None | |
862 | self._startOnUnix() | |
863 | ||
864 | def _runChildOnUnix(self): | |
865 | #XXX Errors running the child do *not* get communicated back. | |
866 | ||
867 | #XXX Perhaps we should *always* prefix with '/bin/sh -c'? There is a | |
868 | # disparity btwn how this works on Linux and Windows. | |
869 | if isinstance(self._cmd, types.StringTypes): | |
870 | # This is easier than trying to reproduce shell interpretation to | |
871 | # separate the arguments. | |
872 | cmd = ['/bin/sh', '-c', self._cmd] | |
873 | else: | |
874 | cmd = self._cmd | |
875 | ||
876 | # Close all file descriptors (except std*) inherited from the parent. | |
877 | MAXFD = 256 # Max number of file descriptors (os.getdtablesize()???) | |
878 | for i in range(3, MAXFD): | |
879 | try: | |
880 | os.close(i) | |
881 | except OSError: | |
882 | pass | |
883 | ||
884 | try: | |
885 | if self._env: | |
886 | os.execvpe(cmd[0], cmd, self._env) | |
887 | else: | |
888 | os.execvp(cmd[0], cmd) | |
889 | finally: | |
890 | os._exit(1) # Should never get here. | |
891 | ||
892 | def _forkAndExecChildOnUnix(self): | |
893 | """Fork and start the child process. | |
894 | ||
895 | Sets self._pid as a side effect. | |
896 | """ | |
897 | pid = os.fork() | |
898 | if pid == 0: # child | |
899 | self._runChildOnUnix() | |
900 | # parent | |
901 | self._pid = pid | |
902 | ||
903 | def _startOnUnix(self): | |
904 | if self._cwd: | |
905 | oldDir = os.getcwd() | |
906 | try: | |
907 | os.chdir(self._cwd) | |
908 | except OSError, ex: | |
909 | raise ProcessError(msg=str(ex), errno=ex.errno) | |
910 | self._forkAndExecChildOnUnix() | |
911 | ||
912 | # parent | |
913 | if self._cwd: | |
914 | os.chdir(oldDir) | |
915 | ||
916 | def _startOnWindows(self): | |
917 | if type(self._cmd) in (types.ListType, types.TupleType): | |
918 | # And arg vector was passed in. | |
919 | cmd = _joinArgv(self._cmd) | |
920 | else: | |
921 | cmd = self._cmd | |
922 | ||
923 | si = win32process.STARTUPINFO() | |
924 | si.dwFlags = win32process.STARTF_USESHOWWINDOW | |
925 | si.wShowWindow = SW_SHOWDEFAULT | |
926 | ||
927 | if not (self._flags & self.CREATE_NEW_CONSOLE): | |
928 | #XXX This is hacky. | |
929 | # We cannot then use _fixupCommand because this will cause a | |
930 | # shell to be openned as the command is launched. Therefore need | |
931 | # to ensure be have the full path to the executable to launch. | |
932 | try: | |
933 | cmd = _whichFirstArg(cmd, self._env) | |
934 | except ProcessError: | |
935 | # Could not find the command, perhaps it is an internal | |
936 | # shell command -- fallback to _fixupCommand | |
937 | cmd = _fixupCommand(cmd, self._env) | |
938 | else: | |
939 | cmd = _fixupCommand(cmd, self._env) | |
940 | log.debug("cmd = %r", cmd) | |
941 | ||
942 | # Start the child process. | |
943 | try: | |
944 | self._hProcess, self._hThread, self._processId, self._threadId\ | |
945 | = _SaferCreateProcess( | |
946 | None, # app name | |
947 | cmd, # command line | |
948 | None, # process security attributes | |
949 | None, # primary thread security attributes | |
950 | 0, # handles are inherited | |
951 | self._flags, # creation flags | |
952 | self._env, # environment | |
953 | self._cwd, # current working directory | |
954 | si) # STARTUPINFO pointer | |
955 | win32api.CloseHandle(self._hThread) | |
956 | except win32api.error, ex: | |
957 | raise ProcessError(msg="Error creating process for '%s': %s"\ | |
958 | % (cmd, ex.args[2]), | |
959 | errno=ex.args[0]) | |
960 | ||
961 | def wait(self, timeout=None): | |
962 | """Wait for the started process to complete. | |
963 | ||
964 | "timeout" (on Windows) is a floating point number of seconds after | |
965 | which to timeout. Default is win32event.INFINITE. | |
966 | "timeout" (on Unix) is akin to the os.waitpid() "options" argument | |
967 | (os.WNOHANG may be used to return immediately if the process has | |
968 | not exited). Default is 0, i.e. wait forever. | |
969 | ||
970 | If the wait time's out it will raise a ProcessError. Otherwise it | |
971 | will return the child's exit value (on Windows) or the child's exit | |
972 | status excoded as per os.waitpid() (on Linux): | |
973 | "a 16-bit number, whose low byte is the signal number that killed | |
974 | the process, and whose high byte is the exit status (if the | |
975 | signal number is zero); the high bit of the low byte is set if a | |
976 | core file was produced." | |
977 | In the latter case, use the os.W*() methods to interpret the return | |
978 | value. | |
979 | """ | |
980 | # XXX Or should returning the exit value be move out to another | |
981 | # function as on Win32 process control? If so, then should | |
982 | # perhaps not make WaitForSingleObject semantic transformation. | |
983 | if sys.platform.startswith("win"): | |
984 | if timeout is None: | |
985 | timeout = win32event.INFINITE | |
986 | else: | |
987 | timeout = timeout * 1000.0 # Win32 API's timeout is in millisecs | |
988 | ||
989 | rc = win32event.WaitForSingleObject(self._hProcess, timeout) | |
990 | if rc == win32event.WAIT_FAILED: | |
991 | raise ProcessError("'WAIT_FAILED' when waiting for process to "\ | |
992 | "terminate: %r" % self._cmd, rc) | |
993 | elif rc == win32event.WAIT_TIMEOUT: | |
994 | raise ProcessError("'WAIT_TIMEOUT' when waiting for process to "\ | |
995 | "terminate: %r" % self._cmd, rc) | |
996 | ||
997 | retval = win32process.GetExitCodeProcess(self._hProcess) | |
998 | else: | |
999 | # os.waitpid() will raise: | |
1000 | # OSError: [Errno 10] No child processes | |
1001 | # on subsequent .wait() calls. Change these semantics to have | |
1002 | # subsequent .wait() calls return the exit status and return | |
1003 | # immediately without raising an exception. | |
1004 | # (XXX It would require synchronization code to handle the case | |
1005 | # of multiple simultaneous .wait() requests, however we can punt | |
1006 | # on that because it is moot while Linux still has the problem | |
1007 | # for which _ThreadFixer() exists.) | |
1008 | if self.__retvalCache is not None: | |
1009 | retval = self.__retvalCache | |
1010 | else: | |
1011 | if timeout is None: | |
1012 | timeout = 0 | |
1013 | pid, sts = os.waitpid(self._pid, timeout) | |
1014 | if pid == self._pid: | |
1015 | self.__retvalCache = retval = sts | |
1016 | else: | |
1017 | raise ProcessError("Wait for process timed out.", | |
1018 | self.WAIT_TIMEOUT) | |
1019 | return retval | |
1020 | ||
1021 | def kill(self, exitCode=0, gracePeriod=1.0, sig=None): | |
1022 | """Kill process. | |
1023 | ||
1024 | "exitCode" [deprecated, not supported] (Windows only) is the | |
1025 | code the terminated process should exit with. | |
1026 | "gracePeriod" (Windows only) is a number of seconds the process is | |
1027 | allowed to shutdown with a WM_CLOSE signal before a hard | |
1028 | terminate is called. | |
1029 | "sig" (Unix only) is the signal to use to kill the process. Defaults | |
1030 | to signal.SIGKILL. See os.kill() for more information. | |
1031 | ||
1032 | Windows: | |
1033 | Try for an orderly shutdown via WM_CLOSE. If still running | |
1034 | after gracePeriod (1 sec. default), terminate. | |
1035 | """ | |
1036 | if sys.platform.startswith("win"): | |
1037 | import win32gui | |
1038 | # Send WM_CLOSE to windows in this process group. | |
1039 | win32gui.EnumWindows(self._close_, 0) | |
1040 | ||
1041 | # Send Ctrl-Break signal to all processes attached to this | |
1042 | # console. This is supposed to trigger shutdown handlers in | |
1043 | # each of the processes. | |
1044 | try: | |
1045 | win32api.GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, | |
1046 | self._processId) | |
1047 | except AttributeError: | |
1048 | log.warn("The win32api module does not have "\ | |
1049 | "GenerateConsoleCtrlEvent(). This may mean that "\ | |
1050 | "parts of this process group have NOT been killed.") | |
1051 | except win32api.error, ex: | |
1052 | if ex.args[0] not in (6, 87): | |
1053 | # Ignore the following: | |
1054 | # api_error: (87, 'GenerateConsoleCtrlEvent', 'The parameter is incorrect.') | |
1055 | # api_error: (6, 'GenerateConsoleCtrlEvent', 'The handle is invalid.') | |
1056 | # Get error 6 if there is no console. | |
1057 | raise | |
1058 | ||
1059 | # Last resort: call TerminateProcess if it has not yet. | |
1060 | retval = 0 | |
1061 | try: | |
1062 | self.wait(gracePeriod) | |
1063 | except ProcessError, ex: | |
1064 | log.info("[%s] Process.kill: calling TerminateProcess", id(self)) | |
1065 | win32process.TerminateProcess(self._hProcess, -1) | |
1066 | win32api.Sleep(100) # wait for resources to be released | |
1067 | ||
1068 | else: | |
1069 | if sig is None: | |
1070 | sig = signal.SIGKILL | |
1071 | try: | |
1072 | os.kill(self._pid, sig) | |
1073 | except OSError, ex: | |
1074 | if ex.errno != 3: | |
1075 | # Ignore: OSError: [Errno 3] No such process | |
1076 | raise | |
1077 | ||
1078 | def _close_(self, hwnd, dummy): | |
1079 | """Callback used by .kill() on Windows. | |
1080 | ||
1081 | EnumWindows callback - sends WM_CLOSE to any window owned by this | |
1082 | process. | |
1083 | """ | |
1084 | threadId, processId = win32process.GetWindowThreadProcessId(hwnd) | |
1085 | if processId == self._processId: | |
1086 | import win32gui | |
1087 | win32gui.PostMessage(hwnd, WM_CLOSE, 0, 0) | |
1088 | ||
1089 | ||
1090 | class ProcessOpen(Process): | |
1091 | """Create a process and setup pipes to it standard handles. | |
1092 | ||
1093 | This is a super popen3. | |
1094 | """ | |
1095 | # TODO: | |
1096 | # - Share some implementation with Process and ProcessProxy. | |
1097 | # | |
1098 | ||
1099 | def __init__(self, cmd, mode='t', cwd=None, env=None): | |
1100 | """Create a Process with proxy threads for each std handle. | |
1101 | ||
1102 | "cmd" is the command string or argument vector to run. | |
1103 | "mode" (Windows only) specifies whether the pipes used to communicate | |
1104 | with the child are openned in text, 't', or binary, 'b', mode. | |
1105 | This is ignored on platforms other than Windows. Default is 't'. | |
1106 | "cwd" optionally specifies the directory in which the child process | |
1107 | should be started. Default is None, a.k.a. inherits the cwd from | |
1108 | the parent. | |
1109 | "env" is optionally a mapping specifying the environment in which to | |
1110 | start the child. Default is None, a.k.a. inherits the environment | |
1111 | of the parent. | |
1112 | """ | |
1113 | # Keep a reference to ensure it is around for this object's destruction. | |
1114 | self.__log = log | |
1115 | log.info("ProcessOpen.__init__(cmd=%r, mode=%r, cwd=%r, env=%r)", | |
1116 | cmd, mode, cwd, env) | |
1117 | self._cmd = cmd | |
1118 | if not self._cmd: | |
1119 | raise ProcessError("You must specify a command.") | |
1120 | self._cwd = cwd | |
1121 | self._env = env | |
1122 | self._mode = mode | |
1123 | if self._mode not in ('t', 'b'): | |
1124 | raise ProcessError("'mode' must be 't' or 'b'.") | |
1125 | self._closed = 0 | |
1126 | ||
1127 | if sys.platform.startswith("win"): | |
1128 | self._startOnWindows() | |
1129 | else: | |
1130 | self.__retvalCache = None | |
1131 | self._startOnUnix() | |
1132 | ||
1133 | _registerProcess(self) | |
1134 | ||
1135 | def __del__(self): | |
1136 | #XXX Should probably not rely upon this. | |
1137 | logres.info("[%s] ProcessOpen.__del__()", id(self)) | |
1138 | self.close() | |
1139 | del self.__log # drop reference | |
1140 | ||
1141 | def close(self): | |
1142 | if not self._closed: | |
1143 | self.__log.info("[%s] ProcessOpen.close()" % id(self)) | |
1144 | ||
1145 | # Ensure that all IOBuffer's are closed. If they are not, these | |
1146 | # can cause hangs. | |
1147 | try: | |
1148 | self.__log.info("[%s] ProcessOpen: closing stdin (%r)."\ | |
1149 | % (id(self), self.stdin)) | |
1150 | self.stdin.close() | |
1151 | except AttributeError: | |
1152 | # May not have gotten far enough in the __init__ to set | |
1153 | # self.stdin, etc. | |
1154 | pass | |
1155 | try: | |
1156 | self.__log.info("[%s] ProcessOpen: closing stdout (%r)."\ | |
1157 | % (id(self), self.stdout)) | |
1158 | self.stdout.close() | |
1159 | except AttributeError: | |
1160 | # May not have gotten far enough in the __init__ to set | |
1161 | # self.stdout, etc. | |
1162 | pass | |
1163 | try: | |
1164 | self.__log.info("[%s] ProcessOpen: closing stderr (%r)."\ | |
1165 | % (id(self), self.stderr)) | |
1166 | self.stderr.close() | |
1167 | except AttributeError: | |
1168 | # May not have gotten far enough in the __init__ to set | |
1169 | # self.stderr, etc. | |
1170 | pass | |
1171 | ||
1172 | self._closed = 1 | |
1173 | ||
1174 | def _forkAndExecChildOnUnix(self, fdChildStdinRd, fdChildStdoutWr, | |
1175 | fdChildStderrWr): | |
1176 | """Fork and start the child process. | |
1177 | ||
1178 | Sets self._pid as a side effect. | |
1179 | """ | |
1180 | pid = os.fork() | |
1181 | if pid == 0: # child | |
1182 | os.dup2(fdChildStdinRd, 0) | |
1183 | os.dup2(fdChildStdoutWr, 1) | |
1184 | os.dup2(fdChildStderrWr, 2) | |
1185 | self._runChildOnUnix() | |
1186 | # parent | |
1187 | self._pid = pid | |
1188 | ||
1189 | def _startOnUnix(self): | |
1190 | # Create pipes for std handles. | |
1191 | fdChildStdinRd, fdChildStdinWr = os.pipe() | |
1192 | fdChildStdoutRd, fdChildStdoutWr = os.pipe() | |
1193 | fdChildStderrRd, fdChildStderrWr = os.pipe() | |
1194 | ||
1195 | if self._cwd: | |
1196 | oldDir = os.getcwd() | |
1197 | try: | |
1198 | os.chdir(self._cwd) | |
1199 | except OSError, ex: | |
1200 | raise ProcessError(msg=str(ex), errno=ex.errno) | |
1201 | self._forkAndExecChildOnUnix(fdChildStdinRd, fdChildStdoutWr, | |
1202 | fdChildStderrWr) | |
1203 | if self._cwd: | |
1204 | os.chdir(oldDir) | |
1205 | ||
1206 | os.close(fdChildStdinRd) | |
1207 | os.close(fdChildStdoutWr) | |
1208 | os.close(fdChildStderrWr) | |
1209 | ||
1210 | self.stdin = _FileWrapper(descriptor=fdChildStdinWr) | |
1211 | logres.info("[%s] ProcessOpen._start(): create child stdin: %r", | |
1212 | id(self), self.stdin) | |
1213 | self.stdout = _FileWrapper(descriptor=fdChildStdoutRd) | |
1214 | logres.info("[%s] ProcessOpen._start(): create child stdout: %r", | |
1215 | id(self), self.stdout) | |
1216 | self.stderr = _FileWrapper(descriptor=fdChildStderrRd) | |
1217 | logres.info("[%s] ProcessOpen._start(): create child stderr: %r", | |
1218 | id(self), self.stderr) | |
1219 | ||
1220 | def _startOnWindows(self): | |
1221 | if type(self._cmd) in (types.ListType, types.TupleType): | |
1222 | # An arg vector was passed in. | |
1223 | cmd = _joinArgv(self._cmd) | |
1224 | else: | |
1225 | cmd = self._cmd | |
1226 | ||
1227 | # Create pipes for std handles. | |
1228 | # (Set the bInheritHandle flag so pipe handles are inherited.) | |
1229 | saAttr = pywintypes.SECURITY_ATTRIBUTES() | |
1230 | saAttr.bInheritHandle = 1 | |
1231 | #XXX Should maybe try with os.pipe. Dunno what that does for | |
1232 | # inheritability though. | |
1233 | hChildStdinRd, hChildStdinWr = win32pipe.CreatePipe(saAttr, 0) | |
1234 | hChildStdoutRd, hChildStdoutWr = win32pipe.CreatePipe(saAttr, 0) | |
1235 | hChildStderrRd, hChildStderrWr = win32pipe.CreatePipe(saAttr, 0) | |
1236 | ||
1237 | try: | |
1238 | # Duplicate the parent ends of the pipes so they are not | |
1239 | # inherited. | |
1240 | hChildStdinWrDup = win32api.DuplicateHandle( | |
1241 | win32api.GetCurrentProcess(), | |
1242 | hChildStdinWr, | |
1243 | win32api.GetCurrentProcess(), | |
1244 | 0, | |
1245 | 0, # not inherited | |
1246 | DUPLICATE_SAME_ACCESS) | |
1247 | win32api.CloseHandle(hChildStdinWr) | |
1248 | self._hChildStdinWr = hChildStdinWrDup | |
1249 | hChildStdoutRdDup = win32api.DuplicateHandle( | |
1250 | win32api.GetCurrentProcess(), | |
1251 | hChildStdoutRd, | |
1252 | win32api.GetCurrentProcess(), | |
1253 | 0, | |
1254 | 0, # not inherited | |
1255 | DUPLICATE_SAME_ACCESS) | |
1256 | win32api.CloseHandle(hChildStdoutRd) | |
1257 | self._hChildStdoutRd = hChildStdoutRdDup | |
1258 | hChildStderrRdDup = win32api.DuplicateHandle( | |
1259 | win32api.GetCurrentProcess(), | |
1260 | hChildStderrRd, | |
1261 | win32api.GetCurrentProcess(), | |
1262 | 0, | |
1263 | 0, # not inherited | |
1264 | DUPLICATE_SAME_ACCESS) | |
1265 | win32api.CloseHandle(hChildStderrRd) | |
1266 | self._hChildStderrRd = hChildStderrRdDup | |
1267 | ||
1268 | # Set the translation mode and buffering. | |
1269 | if self._mode == 't': | |
1270 | flags = os.O_TEXT | |
1271 | else: | |
1272 | flags = 0 | |
1273 | fdChildStdinWr = msvcrt.open_osfhandle(self._hChildStdinWr, flags) | |
1274 | fdChildStdoutRd = msvcrt.open_osfhandle(self._hChildStdoutRd, flags) | |
1275 | fdChildStderrRd = msvcrt.open_osfhandle(self._hChildStderrRd, flags) | |
1276 | ||
1277 | self.stdin = _FileWrapper(descriptor=fdChildStdinWr, | |
1278 | handle=self._hChildStdinWr) | |
1279 | logres.info("[%s] ProcessOpen._start(): create child stdin: %r", | |
1280 | id(self), self.stdin) | |
1281 | self.stdout = _FileWrapper(descriptor=fdChildStdoutRd, | |
1282 | handle=self._hChildStdoutRd) | |
1283 | logres.info("[%s] ProcessOpen._start(): create child stdout: %r", | |
1284 | id(self), self.stdout) | |
1285 | self.stderr = _FileWrapper(descriptor=fdChildStderrRd, | |
1286 | handle=self._hChildStderrRd) | |
1287 | logres.info("[%s] ProcessOpen._start(): create child stderr: %r", | |
1288 | id(self), self.stderr) | |
1289 | ||
1290 | # Start the child process. | |
1291 | si = win32process.STARTUPINFO() | |
1292 | si.dwFlags = win32process.STARTF_USESHOWWINDOW | |
1293 | si.wShowWindow = 0 # SW_HIDE | |
1294 | si.hStdInput = hChildStdinRd | |
1295 | si.hStdOutput = hChildStdoutWr | |
1296 | si.hStdError = hChildStderrWr | |
1297 | si.dwFlags |= win32process.STARTF_USESTDHANDLES | |
1298 | ||
1299 | cmd = _fixupCommand(cmd, self._env) | |
1300 | ||
1301 | creationFlags = win32process.CREATE_NEW_PROCESS_GROUP | |
1302 | try: | |
1303 | self._hProcess, hThread, self._processId, threadId\ | |
1304 | = _SaferCreateProcess( | |
1305 | None, # app name | |
1306 | cmd, # command line | |
1307 | None, # process security attributes | |
1308 | None, # primary thread security attributes | |
1309 | 1, # handles are inherited | |
1310 | creationFlags, # creation flags | |
1311 | self._env, # environment | |
1312 | self._cwd, # current working directory | |
1313 | si) # STARTUPINFO pointer | |
1314 | except win32api.error, ex: | |
1315 | raise ProcessError(msg=ex.args[2], errno=ex.args[0]) | |
1316 | win32api.CloseHandle(hThread) | |
1317 | ||
1318 | finally: | |
1319 | # Close child ends of pipes on the parent's side (the | |
1320 | # parent's ends of the pipe are closed in the _FileWrappers.) | |
1321 | win32file.CloseHandle(hChildStdinRd) | |
1322 | win32file.CloseHandle(hChildStdoutWr) | |
1323 | win32file.CloseHandle(hChildStderrWr) | |
1324 | ||
1325 | def wait(self, timeout=None): | |
1326 | """Wait for the started process to complete. | |
1327 | ||
1328 | "timeout" (on Windows) is a floating point number of seconds after | |
1329 | which to timeout. Default is win32event.INFINITE. | |
1330 | "timeout" (on Unix) is akin to the os.waitpid() "options" argument | |
1331 | (os.WNOHANG may be used to return immediately if the process has | |
1332 | not exited). Default is 0, i.e. wait forever. | |
1333 | ||
1334 | If the wait time's out it will raise a ProcessError. Otherwise it | |
1335 | will return the child's exit value (on Windows) or the child's exit | |
1336 | status excoded as per os.waitpid() (on Linux): | |
1337 | "a 16-bit number, whose low byte is the signal number that killed | |
1338 | the process, and whose high byte is the exit status (if the | |
1339 | signal number is zero); the high bit of the low byte is set if a | |
1340 | core file was produced." | |
1341 | In the latter case, use the os.W*() methods to interpret the return | |
1342 | value. | |
1343 | """ | |
1344 | # XXX Or should returning the exit value be move out to another | |
1345 | # function as on Win32 process control? If so, then should | |
1346 | # perhaps not make WaitForSingleObject semantic | |
1347 | # transformation. | |
1348 | # TODO: | |
1349 | # - Need to rationalize the .wait() API for Windows vs. Unix. | |
1350 | # It is a real pain in the current situation. | |
1351 | if sys.platform.startswith("win"): | |
1352 | if timeout is None: | |
1353 | timeout = win32event.INFINITE | |
1354 | else: | |
1355 | timeout = timeout * 1000.0 # Win32 API's timeout is in millisecs | |
1356 | ||
1357 | #rc = win32event.WaitForSingleObject(self._hProcess, timeout) | |
1358 | rc = win32event.WaitForSingleObject(self._hProcess, int(timeout)) # MATT -- Making timeout an integer | |
1359 | if rc == win32event.WAIT_FAILED: | |
1360 | raise ProcessError("'WAIT_FAILED' when waiting for process to "\ | |
1361 | "terminate: %r" % self._cmd, rc) | |
1362 | elif rc == win32event.WAIT_TIMEOUT: | |
1363 | raise ProcessError("'WAIT_TIMEOUT' when waiting for process to "\ | |
1364 | "terminate: %r" % self._cmd, rc) | |
1365 | ||
1366 | retval = win32process.GetExitCodeProcess(self._hProcess) | |
1367 | else: | |
1368 | # os.waitpid() will raise: | |
1369 | # OSError: [Errno 10] No child processes | |
1370 | # on subsequent .wait() calls. Change these semantics to have | |
1371 | # subsequent .wait() calls return the exit status and return | |
1372 | # immediately without raising an exception. | |
1373 | # (XXX It would require synchronization code to handle the case | |
1374 | # of multiple simultaneous .wait() requests, however we can punt | |
1375 | # on that because it is moot while Linux still has the problem | |
1376 | # for which _ThreadFixer() exists.) | |
1377 | if self.__retvalCache is not None: | |
1378 | retval = self.__retvalCache | |
1379 | else: | |
1380 | if timeout is None: | |
1381 | timeout = 0 | |
1382 | pid, sts = os.waitpid(self._pid, timeout) | |
1383 | if pid == self._pid: | |
1384 | self.__retvalCache = retval = sts | |
1385 | else: | |
1386 | raise ProcessError("Wait for process timed out.", | |
1387 | self.WAIT_TIMEOUT) | |
1388 | _unregisterProcess(self) | |
1389 | return retval | |
1390 | ||
1391 | def kill(self, exitCode=0, gracePeriod=1.0, sig=None): | |
1392 | """Kill process. | |
1393 | ||
1394 | "exitCode" [deprecated, not supported] (Windows only) is the | |
1395 | code the terminated process should exit with. | |
1396 | "gracePeriod" (Windows only) is a number of seconds the process is | |
1397 | allowed to shutdown with a WM_CLOSE signal before a hard | |
1398 | terminate is called. | |
1399 | "sig" (Unix only) is the signal to use to kill the process. Defaults | |
1400 | to signal.SIGKILL. See os.kill() for more information. | |
1401 | ||
1402 | Windows: | |
1403 | Try for an orderly shutdown via WM_CLOSE. If still running | |
1404 | after gracePeriod (1 sec. default), terminate. | |
1405 | """ | |
1406 | if sys.platform.startswith("win"): | |
1407 | import win32gui | |
1408 | # Send WM_CLOSE to windows in this process group. | |
1409 | win32gui.EnumWindows(self._close_, 0) | |
1410 | ||
1411 | # Send Ctrl-Break signal to all processes attached to this | |
1412 | # console. This is supposed to trigger shutdown handlers in | |
1413 | # each of the processes. | |
1414 | try: | |
1415 | win32api.GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, | |
1416 | self._processId) | |
1417 | except AttributeError: | |
1418 | log.warn("The win32api module does not have "\ | |
1419 | "GenerateConsoleCtrlEvent(). This may mean that "\ | |
1420 | "parts of this process group have NOT been killed.") | |
1421 | except win32api.error, ex: | |
1422 | if ex.args[0] not in (6, 87): | |
1423 | # Ignore the following: | |
1424 | # api_error: (87, 'GenerateConsoleCtrlEvent', 'The parameter is incorrect.') | |
1425 | # api_error: (6, 'GenerateConsoleCtrlEvent', 'The handle is invalid.') | |
1426 | # Get error 6 if there is no console. | |
1427 | raise | |
1428 | ||
1429 | # Last resort: call TerminateProcess if it has not yet. | |
1430 | retval = 0 | |
1431 | try: | |
1432 | self.wait(gracePeriod) | |
1433 | except ProcessError, ex: | |
1434 | log.info("[%s] Process.kill: calling TerminateProcess", id(self)) | |
1435 | win32process.TerminateProcess(self._hProcess, -1) | |
1436 | win32api.Sleep(100) # wait for resources to be released | |
1437 | ||
1438 | else: | |
1439 | if sig is None: | |
1440 | sig = signal.SIGKILL | |
1441 | try: | |
1442 | os.kill(self._pid, sig) | |
1443 | except OSError, ex: | |
1444 | if ex.errno != 3: | |
1445 | # Ignore: OSError: [Errno 3] No such process | |
1446 | raise | |
1447 | ||
1448 | _unregisterProcess(self) | |
1449 | ||
1450 | def _close_(self, hwnd, dummy): | |
1451 | """Callback used by .kill() on Windows. | |
1452 | ||
1453 | EnumWindows callback - sends WM_CLOSE to any window owned by this | |
1454 | process. | |
1455 | """ | |
1456 | threadId, processId = win32process.GetWindowThreadProcessId(hwnd) | |
1457 | if processId == self._processId: | |
1458 | import win32gui | |
1459 | win32gui.PostMessage(hwnd, WM_CLOSE, 0, 0) | |
1460 | ||
1461 | ||
1462 | class ProcessProxy(Process): | |
1463 | """Create a process and proxy communication via the standard handles. | |
1464 | """ | |
1465 | #XXX To add to docstring: | |
1466 | # - stdout/stderr proxy handling | |
1467 | # - stdin proxy handling | |
1468 | # - termination | |
1469 | # - how to .start(), i.e. basic usage rules | |
1470 | # - mention that pased in stdin/stdout/stderr objects have to | |
1471 | # implement at least .write (is .write correct for stdin)? | |
1472 | # - if you pass in stdin, stdout, and/or stderr streams it is the | |
1473 | # user's responsibility to close them afterwards. | |
1474 | # - 'cmd' arg can be a command string or an arg vector | |
1475 | # - etc. | |
1476 | #TODO: | |
1477 | # - .suspend() and .resume()? See Win32::Process Perl module. | |
1478 | # | |
1479 | def __init__(self, cmd, mode='t', cwd=None, env=None, | |
1480 | stdin=None, stdout=None, stderr=None): | |
1481 | """Create a Process with proxy threads for each std handle. | |
1482 | ||
1483 | "cmd" is the command string or argument vector to run. | |
1484 | "mode" (Windows only) specifies whether the pipes used to communicate | |
1485 | with the child are openned in text, 't', or binary, 'b', mode. | |
1486 | This is ignored on platforms other than Windows. Default is 't'. | |
1487 | "cwd" optionally specifies the directory in which the child process | |
1488 | should be started. Default is None, a.k.a. inherits the cwd from | |
1489 | the parent. | |
1490 | "env" is optionally a mapping specifying the environment in which to | |
1491 | start the child. Default is None, a.k.a. inherits the environment | |
1492 | of the parent. | |
1493 | "stdin", "stdout", "stderr" can be used to specify objects with | |
1494 | file-like interfaces to handle read (stdout/stderr) and write | |
1495 | (stdin) events from the child. By default a process.IOBuffer | |
1496 | instance is assigned to each handler. IOBuffer may be | |
1497 | sub-classed. See the IOBuffer doc string for more information. | |
1498 | """ | |
1499 | # Keep a reference to ensure it is around for this object's destruction. | |
1500 | self.__log = log | |
1501 | log.info("ProcessProxy.__init__(cmd=%r, mode=%r, cwd=%r, env=%r, "\ | |
1502 | "stdin=%r, stdout=%r, stderr=%r)", | |
1503 | cmd, mode, cwd, env, stdin, stdout, stderr) | |
1504 | self._cmd = cmd | |
1505 | if not self._cmd: | |
1506 | raise ProcessError("You must specify a command.") | |
1507 | self._mode = mode | |
1508 | if self._mode not in ('t', 'b'): | |
1509 | raise ProcessError("'mode' must be 't' or 'b'.") | |
1510 | self._cwd = cwd | |
1511 | self._env = env | |
1512 | if stdin is None: | |
1513 | self.stdin = IOBuffer(name='<stdin>') | |
1514 | else: | |
1515 | self.stdin = stdin | |
1516 | if stdout is None: | |
1517 | self.stdout = IOBuffer(name='<stdout>') | |
1518 | else: | |
1519 | self.stdout = stdout | |
1520 | if stderr is None: | |
1521 | self.stderr = IOBuffer(name='<stderr>') | |
1522 | else: | |
1523 | self.stderr = stderr | |
1524 | self._closed = 0 | |
1525 | ||
1526 | if sys.platform.startswith("win"): | |
1527 | self._startOnWindows() | |
1528 | else: | |
1529 | self.__retvalCache = None | |
1530 | self._startOnUnix() | |
1531 | ||
1532 | _registerProcess(self) | |
1533 | ||
1534 | def __del__(self): | |
1535 | #XXX Should probably not rely upon this. | |
1536 | logres.info("[%s] ProcessProxy.__del__()", id(self)) | |
1537 | self.close() | |
1538 | del self.__log # drop reference | |
1539 | ||
1540 | def close(self): | |
1541 | if not self._closed: | |
1542 | self.__log.info("[%s] ProcessProxy.close()" % id(self)) | |
1543 | ||
1544 | # Ensure that all IOBuffer's are closed. If they are not, these | |
1545 | # can cause hangs. | |
1546 | self.__log.info("[%s] ProcessProxy: closing stdin (%r)."\ | |
1547 | % (id(self), self.stdin)) | |
1548 | try: | |
1549 | self.stdin.close() | |
1550 | self._stdinProxy.join() | |
1551 | except AttributeError: | |
1552 | # May not have gotten far enough in the __init__ to set | |
1553 | # self.stdin, etc. | |
1554 | pass | |
1555 | self.__log.info("[%s] ProcessProxy: closing stdout (%r)."\ | |
1556 | % (id(self), self.stdout)) | |
1557 | try: | |
1558 | self.stdout.close() | |
1559 | if self._stdoutProxy is not threading.currentThread(): | |
1560 | self._stdoutProxy.join() | |
1561 | except AttributeError: | |
1562 | # May not have gotten far enough in the __init__ to set | |
1563 | # self.stdout, etc. | |
1564 | pass | |
1565 | self.__log.info("[%s] ProcessProxy: closing stderr (%r)."\ | |
1566 | % (id(self), self.stderr)) | |
1567 | try: | |
1568 | self.stderr.close() | |
1569 | if self._stderrProxy is not threading.currentThread(): | |
1570 | self._stderrProxy.join() | |
1571 | except AttributeError: | |
1572 | # May not have gotten far enough in the __init__ to set | |
1573 | # self.stderr, etc. | |
1574 | pass | |
1575 | ||
1576 | self._closed = 1 | |
1577 | ||
1578 | def _forkAndExecChildOnUnix(self, fdChildStdinRd, fdChildStdoutWr, | |
1579 | fdChildStderrWr): | |
1580 | """Fork and start the child process. | |
1581 | ||
1582 | Sets self._pid as a side effect. | |
1583 | """ | |
1584 | pid = os.fork() | |
1585 | if pid == 0: # child | |
1586 | os.dup2(fdChildStdinRd, 0) | |
1587 | os.dup2(fdChildStdoutWr, 1) | |
1588 | os.dup2(fdChildStderrWr, 2) | |
1589 | self._runChildOnUnix() | |
1590 | # parent | |
1591 | self._pid = pid | |
1592 | ||
1593 | def _startOnUnix(self): | |
1594 | # Create pipes for std handles. | |
1595 | fdChildStdinRd, fdChildStdinWr = os.pipe() | |
1596 | fdChildStdoutRd, fdChildStdoutWr = os.pipe() | |
1597 | fdChildStderrRd, fdChildStderrWr = os.pipe() | |
1598 | ||
1599 | if self._cwd: | |
1600 | oldDir = os.getcwd() | |
1601 | try: | |
1602 | os.chdir(self._cwd) | |
1603 | except OSError, ex: | |
1604 | raise ProcessError(msg=str(ex), errno=ex.errno) | |
1605 | self._forkAndExecChildOnUnix(fdChildStdinRd, fdChildStdoutWr, | |
1606 | fdChildStderrWr) | |
1607 | if self._cwd: | |
1608 | os.chdir(oldDir) | |
1609 | ||
1610 | os.close(fdChildStdinRd) | |
1611 | os.close(fdChildStdoutWr) | |
1612 | os.close(fdChildStderrWr) | |
1613 | ||
1614 | childStdin = _FileWrapper(descriptor=fdChildStdinWr) | |
1615 | logres.info("[%s] ProcessProxy._start(): create child stdin: %r", | |
1616 | id(self), childStdin) | |
1617 | childStdout = _FileWrapper(descriptor=fdChildStdoutRd) | |
1618 | logres.info("[%s] ProcessProxy._start(): create child stdout: %r", | |
1619 | id(self), childStdout) | |
1620 | childStderr = _FileWrapper(descriptor=fdChildStderrRd) | |
1621 | logres.info("[%s] ProcessProxy._start(): create child stderr: %r", | |
1622 | id(self), childStderr) | |
1623 | ||
1624 | # Create proxy threads for the out pipes. | |
1625 | self._stdinProxy = _InFileProxy(self.stdin, childStdin, name='<stdin>') | |
1626 | self._stdinProxy.start() | |
1627 | # Clean up the parent's side of <stdin> when it is observed that | |
1628 | # the child has closed its side of <stdout> and <stderr>. (This | |
1629 | # is one way of determining when it is appropriate to clean up | |
1630 | # this pipe, with compromises. See the discussion at the top of | |
1631 | # this module.) | |
1632 | closer = _CountingCloser([self.stdin, childStdin, self], 2) | |
1633 | self._stdoutProxy = _OutFileProxy(childStdout, self.stdout, | |
1634 | [closer], | |
1635 | name='<stdout>') | |
1636 | self._stdoutProxy.start() | |
1637 | self._stderrProxy = _OutFileProxy(childStderr, self.stderr, | |
1638 | [closer], | |
1639 | name='<stderr>') | |
1640 | self._stderrProxy.start() | |
1641 | ||
1642 | def _startOnWindows(self): | |
1643 | if type(self._cmd) in (types.ListType, types.TupleType): | |
1644 | # An arg vector was passed in. | |
1645 | cmd = _joinArgv(self._cmd) | |
1646 | else: | |
1647 | cmd = self._cmd | |
1648 | ||
1649 | # Create pipes for std handles. | |
1650 | # (Set the bInheritHandle flag so pipe handles are inherited.) | |
1651 | saAttr = pywintypes.SECURITY_ATTRIBUTES() | |
1652 | saAttr.bInheritHandle = 1 | |
1653 | #XXX Should maybe try with os.pipe. Dunno what that does for | |
1654 | # inheritability though. | |
1655 | hChildStdinRd, hChildStdinWr = win32pipe.CreatePipe(saAttr, 0) | |
1656 | hChildStdoutRd, hChildStdoutWr = win32pipe.CreatePipe(saAttr, 0) | |
1657 | hChildStderrRd, hChildStderrWr = win32pipe.CreatePipe(saAttr, 0) | |
1658 | ||
1659 | try: | |
1660 | # Duplicate the parent ends of the pipes so they are not | |
1661 | # inherited. | |
1662 | hChildStdinWrDup = win32api.DuplicateHandle( | |
1663 | win32api.GetCurrentProcess(), | |
1664 | hChildStdinWr, | |
1665 | win32api.GetCurrentProcess(), | |
1666 | 0, | |
1667 | 0, # not inherited | |
1668 | DUPLICATE_SAME_ACCESS) | |
1669 | win32api.CloseHandle(hChildStdinWr) | |
1670 | self._hChildStdinWr = hChildStdinWrDup | |
1671 | hChildStdoutRdDup = win32api.DuplicateHandle( | |
1672 | win32api.GetCurrentProcess(), | |
1673 | hChildStdoutRd, | |
1674 | win32api.GetCurrentProcess(), | |
1675 | 0, | |
1676 | 0, # not inherited | |
1677 | DUPLICATE_SAME_ACCESS) | |
1678 | win32api.CloseHandle(hChildStdoutRd) | |
1679 | self._hChildStdoutRd = hChildStdoutRdDup | |
1680 | hChildStderrRdDup = win32api.DuplicateHandle( | |
1681 | win32api.GetCurrentProcess(), | |
1682 | hChildStderrRd, | |
1683 | win32api.GetCurrentProcess(), | |
1684 | 0, | |
1685 | 0, # not inherited | |
1686 | DUPLICATE_SAME_ACCESS) | |
1687 | win32api.CloseHandle(hChildStderrRd) | |
1688 | self._hChildStderrRd = hChildStderrRdDup | |
1689 | ||
1690 | # Set the translation mode. | |
1691 | if self._mode == 't': | |
1692 | flags = os.O_TEXT | |
1693 | mode = '' | |
1694 | else: | |
1695 | flags = 0 | |
1696 | mode = 'b' | |
1697 | fdChildStdinWr = msvcrt.open_osfhandle(self._hChildStdinWr, flags) | |
1698 | fdChildStdoutRd = msvcrt.open_osfhandle(self._hChildStdoutRd, flags) | |
1699 | fdChildStderrRd = msvcrt.open_osfhandle(self._hChildStderrRd, flags) | |
1700 | ||
1701 | childStdin = _FileWrapper(descriptor=fdChildStdinWr, | |
1702 | handle=self._hChildStdinWr) | |
1703 | logres.info("[%s] ProcessProxy._start(): create child stdin: %r", | |
1704 | id(self), childStdin) | |
1705 | childStdout = _FileWrapper(descriptor=fdChildStdoutRd, | |
1706 | handle=self._hChildStdoutRd) | |
1707 | logres.info("[%s] ProcessProxy._start(): create child stdout: %r", | |
1708 | id(self), childStdout) | |
1709 | childStderr = _FileWrapper(descriptor=fdChildStderrRd, | |
1710 | handle=self._hChildStderrRd) | |
1711 | logres.info("[%s] ProcessProxy._start(): create child stderr: %r", | |
1712 | id(self), childStderr) | |
1713 | ||
1714 | # Start the child process. | |
1715 | si = win32process.STARTUPINFO() | |
1716 | si.dwFlags = win32process.STARTF_USESHOWWINDOW | |
1717 | si.wShowWindow = 0 # SW_HIDE | |
1718 | si.hStdInput = hChildStdinRd | |
1719 | si.hStdOutput = hChildStdoutWr | |
1720 | si.hStdError = hChildStderrWr | |
1721 | si.dwFlags |= win32process.STARTF_USESTDHANDLES | |
1722 | ||
1723 | cmd = _fixupCommand(cmd, self._env) | |
1724 | log.debug("cmd = %r", cmd) | |
1725 | ||
1726 | creationFlags = win32process.CREATE_NEW_PROCESS_GROUP | |
1727 | try: | |
1728 | self._hProcess, hThread, self._processId, threadId\ | |
1729 | = _SaferCreateProcess( | |
1730 | None, # app name | |
1731 | cmd, # command line | |
1732 | None, # process security attributes | |
1733 | None, # primary thread security attributes | |
1734 | 1, # handles are inherited | |
1735 | creationFlags, # creation flags | |
1736 | self._env, # environment | |
1737 | self._cwd, # current working directory | |
1738 | si) # STARTUPINFO pointer | |
1739 | except win32api.error, ex: | |
1740 | raise ProcessError(msg=ex.args[2], errno=ex.args[0]) | |
1741 | win32api.CloseHandle(hThread) | |
1742 | ||
1743 | finally: | |
1744 | # Close child ends of pipes on the parent's side (the | |
1745 | # parent's ends of the pipe are closed in the _FileWrappers.) | |
1746 | win32file.CloseHandle(hChildStdinRd) | |
1747 | win32file.CloseHandle(hChildStdoutWr) | |
1748 | win32file.CloseHandle(hChildStderrWr) | |
1749 | ||
1750 | # Create proxy threads for the pipes. | |
1751 | self._stdinProxy = _InFileProxy(self.stdin, childStdin, name='<stdin>') | |
1752 | self._stdinProxy.start() | |
1753 | # Clean up the parent's side of <stdin> when it is observed that | |
1754 | # the child has closed its side of <stdout>. (This is one way of | |
1755 | # determining when it is appropriate to clean up this pipe, with | |
1756 | # compromises. See the discussion at the top of this module.) | |
1757 | self._stdoutProxy = _OutFileProxy(childStdout, self.stdout, | |
1758 | [self.stdin, childStdin, self], | |
1759 | name='<stdout>') | |
1760 | self._stdoutProxy.start() | |
1761 | self._stderrProxy = _OutFileProxy(childStderr, self.stderr, | |
1762 | name='<stderr>') | |
1763 | self._stderrProxy.start() | |
1764 | ||
1765 | def wait(self, timeout=None): | |
1766 | """Wait for the started process to complete. | |
1767 | ||
1768 | "timeout" (on Windows) is a floating point number of seconds after | |
1769 | which to timeout. Default is win32event.INFINITE. | |
1770 | "timeout" (on Unix) is akin to the os.waitpid() "options" argument | |
1771 | (os.WNOHANG may be used to return immediately if the process has | |
1772 | not exited). Default is 0, i.e. wait forever. | |
1773 | ||
1774 | If the wait time's out it will raise a ProcessError. Otherwise it | |
1775 | will return the child's exit value (on Windows) or the child's exit | |
1776 | status excoded as per os.waitpid() (on Linux): | |
1777 | "a 16-bit number, whose low byte is the signal number that killed | |
1778 | the process, and whose high byte is the exit status (if the | |
1779 | signal number is zero); the high bit of the low byte is set if a | |
1780 | core file was produced." | |
1781 | In the latter case, use the os.W*() methods to interpret the return | |
1782 | value. | |
1783 | """ | |
1784 | # XXX Or should returning the exit value be move out to another | |
1785 | # function as on Win32 process control? If so, then should | |
1786 | # perhaps not make WaitForSingleObject semantic transformation. | |
1787 | if sys.platform.startswith("win"): | |
1788 | if timeout is None: | |
1789 | timeout = win32event.INFINITE | |
1790 | else: | |
1791 | timeout = timeout * 1000.0 # Win32 API's timeout is in millisecs | |
1792 | ||
1793 | rc = win32event.WaitForSingleObject(self._hProcess, timeout) | |
1794 | if rc == win32event.WAIT_FAILED: | |
1795 | raise ProcessError("'WAIT_FAILED' when waiting for process to "\ | |
1796 | "terminate: %r" % self._cmd, rc) | |
1797 | elif rc == win32event.WAIT_TIMEOUT: | |
1798 | raise ProcessError("'WAIT_TIMEOUT' when waiting for process to "\ | |
1799 | "terminate: %r" % self._cmd, rc) | |
1800 | ||
1801 | retval = win32process.GetExitCodeProcess(self._hProcess) | |
1802 | else: | |
1803 | # os.waitpid() will raise: | |
1804 | # OSError: [Errno 10] No child processes | |
1805 | # on subsequent .wait() calls. Change these semantics to have | |
1806 | # subsequent .wait() calls return the exit status and return | |
1807 | # immediately without raising an exception. | |
1808 | # (XXX It would require synchronization code to handle the case | |
1809 | # of multiple simultaneous .wait() requests, however we can punt | |
1810 | # on that because it is moot while Linux still has the problem | |
1811 | # for which _ThreadFixer() exists.) | |
1812 | if self.__retvalCache is not None: | |
1813 | retval = self.__retvalCache | |
1814 | else: | |
1815 | if timeout is None: | |
1816 | timeout = 0 | |
1817 | pid, sts = os.waitpid(self._pid, timeout) | |
1818 | if pid == self._pid: | |
1819 | self.__retvalCache = retval = sts | |
1820 | else: | |
1821 | raise ProcessError("Wait for process timed out.", | |
1822 | self.WAIT_TIMEOUT) | |
1823 | _unregisterProcess(self) | |
1824 | return retval | |
1825 | ||
1826 | def kill(self, exitCode=0, gracePeriod=1.0, sig=None): | |
1827 | """Kill process. | |
1828 | ||
1829 | "exitCode" [deprecated, not supported] (Windows only) is the | |
1830 | code the terminated process should exit with. | |
1831 | "gracePeriod" (Windows only) is a number of seconds the process is | |
1832 | allowed to shutdown with a WM_CLOSE signal before a hard | |
1833 | terminate is called. | |
1834 | "sig" (Unix only) is the signal to use to kill the process. Defaults | |
1835 | to signal.SIGKILL. See os.kill() for more information. | |
1836 | ||
1837 | Windows: | |
1838 | Try for an orderly shutdown via WM_CLOSE. If still running | |
1839 | after gracePeriod (1 sec. default), terminate. | |
1840 | """ | |
1841 | if sys.platform.startswith("win"): | |
1842 | import win32gui | |
1843 | # Send WM_CLOSE to windows in this process group. | |
1844 | win32gui.EnumWindows(self._close_, 0) | |
1845 | ||
1846 | # Send Ctrl-Break signal to all processes attached to this | |
1847 | # console. This is supposed to trigger shutdown handlers in | |
1848 | # each of the processes. | |
1849 | try: | |
1850 | win32api.GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, | |
1851 | self._processId) | |
1852 | except AttributeError: | |
1853 | log.warn("The win32api module does not have "\ | |
1854 | "GenerateConsoleCtrlEvent(). This may mean that "\ | |
1855 | "parts of this process group have NOT been killed.") | |
1856 | except win32api.error, ex: | |
1857 | if ex.args[0] not in (6, 87): | |
1858 | # Ignore the following: | |
1859 | # api_error: (87, 'GenerateConsoleCtrlEvent', 'The parameter is incorrect.') | |
1860 | # api_error: (6, 'GenerateConsoleCtrlEvent', 'The handle is invalid.') | |
1861 | # Get error 6 if there is no console. | |
1862 | raise | |
1863 | ||
1864 | # Last resort: call TerminateProcess if it has not yet. | |
1865 | retval = 0 | |
1866 | try: | |
1867 | self.wait(gracePeriod) | |
1868 | except ProcessError, ex: | |
1869 | log.info("[%s] Process.kill: calling TerminateProcess", id(self)) | |
1870 | win32process.TerminateProcess(self._hProcess, -1) | |
1871 | win32api.Sleep(100) # wait for resources to be released | |
1872 | ||
1873 | else: | |
1874 | if sig is None: | |
1875 | sig = signal.SIGKILL | |
1876 | try: | |
1877 | os.kill(self._pid, sig) | |
1878 | except OSError, ex: | |
1879 | if ex.errno != 3: | |
1880 | # Ignore: OSError: [Errno 3] No such process | |
1881 | raise | |
1882 | ||
1883 | _unregisterProcess(self) | |
1884 | ||
1885 | def _close_(self, hwnd, dummy): | |
1886 | """Callback used by .kill() on Windows. | |
1887 | ||
1888 | EnumWindows callback - sends WM_CLOSE to any window owned by this | |
1889 | process. | |
1890 | """ | |
1891 | threadId, processId = win32process.GetWindowThreadProcessId(hwnd) | |
1892 | if processId == self._processId: | |
1893 | import win32gui | |
1894 | win32gui.PostMessage(hwnd, WM_CLOSE, 0, 0) | |
1895 | ||
1896 | ||
1897 | class IOBuffer: | |
1898 | """Want to be able to both read and write to this buffer from | |
1899 | difference threads and have the same read/write semantics as for a | |
1900 | std handler. | |
1901 | ||
1902 | This class is subclass-able. _doRead(), _doWrite(), _doReadline(), | |
1903 | _doClose(), _haveLine(), and _haveNumBytes() can be overridden for | |
1904 | specific functionality. The synchronization issues (block on read | |
1905 | until write provides the needed data, termination) are handled for | |
1906 | free. | |
1907 | ||
1908 | Cannot support: | |
1909 | .seek() # Because we are managing *two* positions (one each | |
1910 | .tell() # for reading and writing), these do not make | |
1911 | # sense. | |
1912 | """ | |
1913 | #TODO: | |
1914 | # - Is performance a problem? This will likely be slower that | |
1915 | # StringIO.StringIO(). | |
1916 | # | |
1917 | def __init__(self, mutex=None, stateChange=None, name=None): | |
1918 | """'name' can be set for debugging, it will be used in log messages.""" | |
1919 | if name is not None: | |
1920 | self._name = name | |
1921 | else: | |
1922 | self._name = id(self) | |
1923 | log.info("[%s] IOBuffer.__init__()" % self._name) | |
1924 | ||
1925 | self.__buf = '' | |
1926 | # A state change is defined as the buffer being closed or a | |
1927 | # write occurring. | |
1928 | if mutex is not None: | |
1929 | self._mutex = mutex | |
1930 | else: | |
1931 | self._mutex = threading.Lock() | |
1932 | if stateChange is not None: | |
1933 | self._stateChange = stateChange | |
1934 | else: | |
1935 | self._stateChange = threading.Condition() | |
1936 | self._closed = 0 | |
1937 | ||
1938 | def _doWrite(self, s): | |
1939 | self.__buf += s # Append to buffer. | |
1940 | ||
1941 | def write(self, s): | |
1942 | log.info("[%s] IOBuffer.write(s=%r)", self._name, s) | |
1943 | # Silently drop writes after the buffer has been close()'d. | |
1944 | if self._closed: | |
1945 | return | |
1946 | # If empty write, close buffer (mimicking behaviour from | |
1947 | # koprocess.cpp.) | |
1948 | if not s: | |
1949 | self.close() | |
1950 | return | |
1951 | ||
1952 | self._mutex.acquire() | |
1953 | self._doWrite(s) | |
1954 | self._stateChange.acquire() | |
1955 | self._stateChange.notifyAll() # Notify of the write(). | |
1956 | self._stateChange.release() | |
1957 | self._mutex.release() | |
1958 | ||
1959 | def writelines(self, list): | |
1960 | self.write(''.join(list)) | |
1961 | ||
1962 | def _doRead(self, n): | |
1963 | """Pop 'n' bytes from the internal buffer and return them.""" | |
1964 | if n < 0: | |
1965 | idx = len(self.__buf) | |
1966 | else: | |
1967 | idx = min(n, len(self.__buf)) | |
1968 | retval, self.__buf = self.__buf[:idx], self.__buf[idx:] | |
1969 | return retval | |
1970 | ||
1971 | def read(self, n=-1): | |
1972 | log.info("[%s] IOBuffer.read(n=%r)" % (self._name, n)) | |
1973 | log.info("[%s] IOBuffer.read(): wait for data" % self._name) | |
1974 | if n < 0: | |
1975 | # Wait until the buffer is closed, i.e. no more writes will | |
1976 | # come. | |
1977 | while 1: | |
1978 | if self._closed: break | |
1979 | #log.debug("[%s] <<< IOBuffer.read: state change .wait()"\ | |
1980 | # % self._name) | |
1981 | self._stateChange.acquire() | |
1982 | self._stateChange.wait() | |
1983 | self._stateChange.release() | |
1984 | #log.debug("[%s] >>> IOBuffer.read: done change .wait()"\ | |
1985 | # % self._name) | |
1986 | else: | |
1987 | # Wait until there are the requested number of bytes to read | |
1988 | # (or until the buffer is closed, i.e. no more writes will | |
1989 | # come). | |
1990 | # XXX WARNING: I *think* there is a race condition around | |
1991 | # here whereby self.fparent.read() in _InFileProxy can | |
1992 | # hang. *Sometime* test_stdin::test_stdin_buffer() will | |
1993 | # hang. This was *before* I moved the | |
1994 | # _stateChange.acquire() and .release() calls out side | |
1995 | # of the 'while 1:' here. ...and now they are back | |
1996 | # inside. | |
1997 | while 1: | |
1998 | if self._closed: break | |
1999 | if self._haveNumBytes(n): break | |
2000 | #log.debug("[%s] <<< IOBuffer.read: state change .wait()"\ | |
2001 | # % self._name) | |
2002 | self._stateChange.acquire() | |
2003 | self._stateChange.wait() | |
2004 | self._stateChange.release() | |
2005 | #log.debug("[%s] >>> IOBuffer.read: done change .wait()"\ | |
2006 | # % self._name) | |
2007 | log.info("[%s] IOBuffer.read(): done waiting for data" % self._name) | |
2008 | ||
2009 | self._mutex.acquire() | |
2010 | retval = self._doRead(n) | |
2011 | self._mutex.release() | |
2012 | return retval | |
2013 | ||
2014 | def _doReadline(self, n): | |
2015 | """Pop the front line (or n bytes of it, whichever is less) from | |
2016 | the internal buffer and return it. | |
2017 | """ | |
2018 | idx = self.__buf.find('\n') | |
2019 | if idx == -1: | |
2020 | idx = len(self.__buf) | |
2021 | else: | |
2022 | idx += 1 # include the '\n' | |
2023 | if n is not None: | |
2024 | idx = min(idx, n) | |
2025 | retval, self.__buf = self.__buf[:idx], self.__buf[idx:] | |
2026 | return retval | |
2027 | ||
2028 | def _haveLine(self): | |
2029 | return self.__buf.find('\n') != -1 | |
2030 | ||
2031 | def _haveNumBytes(self, n=None): | |
2032 | return len(self.__buf) >= n | |
2033 | ||
2034 | def readline(self, n=None): | |
2035 | # Wait until there is a full line (or at least 'n' bytes) | |
2036 | # in the buffer or until the buffer is closed, i.e. no more | |
2037 | # writes will come. | |
2038 | log.info("[%s] IOBuffer.readline(n=%r)" % (self._name, n)) | |
2039 | ||
2040 | log.info("[%s] IOBuffer.readline(): wait for data" % self._name) | |
2041 | while 1: | |
2042 | if self._closed: break | |
2043 | if self._haveLine(): break | |
2044 | if n is not None and self._haveNumBytes(n): break | |
2045 | self._stateChange.acquire() | |
2046 | self._stateChange.wait() | |
2047 | self._stateChange.release() | |
2048 | log.info("[%s] IOBuffer.readline(): done waiting for data"\ | |
2049 | % self._name) | |
2050 | ||
2051 | self._mutex.acquire() | |
2052 | retval = self._doReadline(n) | |
2053 | self._mutex.release() | |
2054 | return retval | |
2055 | ||
2056 | def readlines(self): | |
2057 | lines = [] | |
2058 | while 1: | |
2059 | line = self.readline() | |
2060 | if line: | |
2061 | lines.append(line) | |
2062 | else: | |
2063 | break | |
2064 | return lines | |
2065 | ||
2066 | def _doClose(self): | |
2067 | pass | |
2068 | ||
2069 | def close(self): | |
2070 | if not self._closed: | |
2071 | log.info("[%s] IOBuffer.close()" % self._name) | |
2072 | self._doClose() | |
2073 | self._closed = 1 | |
2074 | self._stateChange.acquire() | |
2075 | self._stateChange.notifyAll() # Notify of the close(). | |
2076 | self._stateChange.release() | |
2077 | ||
2078 | def flush(self): | |
2079 | log.info("[%s] IOBuffer.flush()" % self._name) | |
2080 | #XXX Perhaps flush() should unwedged possible waiting .read() | |
2081 | # and .readline() calls that are waiting for more data??? | |
2082 | ||
2083 | ||
2084 | class _InFileProxy(threading.Thread): | |
2085 | """A thread to proxy stdin.write()'s from the parent to the child.""" | |
2086 | def __init__(self, fParent, fChild, name=None): | |
2087 | """ | |
2088 | "fParent" is a Python file-like object setup for writing. | |
2089 | "fChild" is a Win32 handle to the a child process' output pipe. | |
2090 | "name" can be set for debugging, it will be used in log messages. | |
2091 | """ | |
2092 | log.info("[%s, %s] _InFileProxy.__init__(fChild=%r, fParent=%r)", | |
2093 | name, id(self), fChild, fParent) | |
2094 | threading.Thread.__init__(self, name=name) | |
2095 | self.fChild = fChild | |
2096 | self.fParent = fParent | |
2097 | ||
2098 | def run(self): | |
2099 | log.info("[%s] _InFileProxy: start" % self.getName()) | |
2100 | try: | |
2101 | self._proxyFromParentToChild() | |
2102 | finally: | |
2103 | log.info("[%s] _InFileProxy: closing parent (%r)"\ | |
2104 | % (self.getName(), self.fParent)) | |
2105 | try: | |
2106 | self.fParent.close() | |
2107 | except IOError: | |
2108 | pass # Ignore: IOError: [Errno 4] Interrupted system call | |
2109 | log.info("[%s] _InFileProxy: done" % self.getName()) | |
2110 | ||
2111 | def _proxyFromParentToChild(self): | |
2112 | CHUNKSIZE = 4096 | |
2113 | # Read output from the child process, and (for now) just write | |
2114 | # it out. | |
2115 | while 1: | |
2116 | log.info("[%s] _InFileProxy: waiting for read on parent (%r)"\ | |
2117 | % (self.getName(), self.fParent)) | |
2118 | # XXX Get hangs here (!) even with | |
2119 | # self.stdin.close() in ProcessProxy' __del__() under this | |
2120 | # cond: | |
2121 | # p = ProcessProxy([...], stdin=sys.stdin) | |
2122 | # The user must manually send '\n' via <Enter> or EOF | |
2123 | # via <Ctrl-Z> to unlock this. How to get around that? | |
2124 | # See cleanOnTermination note in _OutFileProxy.run() | |
2125 | # below. | |
2126 | #log.debug("XXX -> start read on %r" % self.fParent) | |
2127 | try: | |
2128 | text = self.fParent.read(CHUNKSIZE) | |
2129 | except ValueError, ex: | |
2130 | # ValueError is raised with trying to write to a closed | |
2131 | # file/pipe. | |
2132 | text = None | |
2133 | #log.debug("XXX <- done read on %r" % self.fParent) | |
2134 | if not text: | |
2135 | # Empty text signifies that the pipe has been closed on | |
2136 | # the parent's end. | |
2137 | log.info("[%s] _InFileProxy: observed close of parent (%r)"\ | |
2138 | % (self.getName(), self.fParent)) | |
2139 | # Signal the child so it knows to stop listening. | |
2140 | try: | |
2141 | logres.info("[%s] _InFileProxy: closing child after "\ | |
2142 | "observing parent's close: %r", self.getName(), | |
2143 | self.fChild) | |
2144 | try: | |
2145 | self.fChild.close() | |
2146 | except IOError: | |
2147 | pass # Ignore: IOError: [Errno 4] Interrupted system call | |
2148 | except IOError, ex: | |
2149 | # Ignore: IOError: [Errno 9] Bad file descriptor | |
2150 | # XXX Do we *know* we want to do that? | |
2151 | pass | |
2152 | break | |
2153 | else: | |
2154 | log.info("[%s] _InFileProxy: read %d bytes from parent: %r"\ | |
2155 | % (self.getName(), len(text), text)) | |
2156 | ||
2157 | log.info("[%s, %s] _InFileProxy: writing %r to child (%r)", | |
2158 | self.getName(), id(self), text, self.fChild) | |
2159 | try: | |
2160 | self.fChild.write(text) | |
2161 | except (OSError, IOError), ex: | |
2162 | # Ignore errors for now. For example: | |
2163 | # - Get this on Win9x when writing multiple lines to "dir": | |
2164 | # OSError: [Errno 32] Broken pipe | |
2165 | #XXX There *may* be errors we don't want to avoid. | |
2166 | #XXX Should maybe just ignore EnvironmentError (base class). | |
2167 | log.info("[%s] _InFileProxy: error writing to child (%r), "\ | |
2168 | "closing: %s" % (self.getName(), self.fParent, ex)) | |
2169 | break | |
2170 | log.info("[%s] _InFileProxy: wrote %d bytes to child: %r"\ | |
2171 | % (self.getName(), len(text), text)) | |
2172 | ||
2173 | ||
2174 | class _OutFileProxy(threading.Thread): | |
2175 | """A thread to watch an "out" file from the spawned child process | |
2176 | and pass on write's to the parent. | |
2177 | """ | |
2178 | def __init__(self, fChild, fParent, toClose=[], name=None): | |
2179 | """ | |
2180 | "fChild" is a Win32 handle to the a child process' output pipe. | |
2181 | "fParent" is a Python file-like object setup for writing. | |
2182 | "toClose" is a list of objects on which to call .close when this | |
2183 | proxy is terminating. | |
2184 | "name" can be set for debugging, it will be used in log messages. | |
2185 | """ | |
2186 | log.info("[%s] _OutFileProxy.__init__(fChild=%r, fParent=%r, "\ | |
2187 | "toClose=%r)", name, fChild, fParent, toClose) | |
2188 | threading.Thread.__init__(self, name=name) | |
2189 | self.fChild = fChild | |
2190 | self.fParent = fParent | |
2191 | self.toClose = toClose | |
2192 | ||
2193 | def run(self): | |
2194 | log.info("[%s] _OutFileProxy: start" % self.getName()) | |
2195 | try: | |
2196 | self._proxyFromChildToParent() | |
2197 | finally: | |
2198 | logres.info("[%s] _OutFileProxy: terminating, close child (%r)", | |
2199 | self.getName(), self.fChild) | |
2200 | try: | |
2201 | self.fChild.close() | |
2202 | except IOError: | |
2203 | pass # Ignore: IOError: [Errno 4] Interrupted system call | |
2204 | log.info("[%s] _OutFileProxy: closing parent (%r)", | |
2205 | self.getName(), self.fParent) | |
2206 | try: | |
2207 | self.fParent.close() | |
2208 | except IOError: | |
2209 | pass # Ignore: IOError: [Errno 4] Interrupted system call | |
2210 | while self.toClose: | |
2211 | logres.info("[%s] _OutFileProxy: closing %r after "\ | |
2212 | "closing parent", self.getName(), self.toClose[0]) | |
2213 | try: | |
2214 | self.toClose[0].close() | |
2215 | except IOError: | |
2216 | pass # Ignore: IOError: [Errno 4] Interrupted system call | |
2217 | del self.toClose[0] | |
2218 | log.info("[%s] _OutFileProxy: done" % self.getName()) | |
2219 | ||
2220 | def _proxyFromChildToParent(self): | |
2221 | CHUNKSIZE = 4096 | |
2222 | # Read output from the child process, and (for now) just write | |
2223 | # it out. | |
2224 | while 1: | |
2225 | text = None | |
2226 | try: | |
2227 | log.info("[%s] _OutFileProxy: waiting for read on child (%r)"\ | |
2228 | % (self.getName(), self.fChild)) | |
2229 | text = self.fChild.read(CHUNKSIZE) | |
2230 | except IOError, ex: | |
2231 | # Ignore: IOError: [Errno 9] Bad file descriptor | |
2232 | # XXX Do we *know* we want to do that? | |
2233 | log.info("[%s] _OutFileProxy: error reading from child (%r), "\ | |
2234 | "shutting down: %s", self.getName(), self.fChild, ex) | |
2235 | break | |
2236 | if not text: | |
2237 | # Empty text signifies that the pipe has been closed on | |
2238 | # the child's end. | |
2239 | log.info("[%s] _OutFileProxy: observed close of child (%r)"\ | |
2240 | % (self.getName(), self.fChild)) | |
2241 | break | |
2242 | ||
2243 | log.info("[%s] _OutFileProxy: text(len=%d): %r", | |
2244 | self.getName(), len(text), text) | |
2245 | self.fParent.write(text) | |
2246 | ||
2247 | ||
2248 | ||
2249 | if sys.platform.startswith("linux"): | |
2250 | class _ThreadFixer: | |
2251 | """Mixin class for various classes in the Process hierarchy to | |
2252 | work around the known LinuxThreads bug where one cannot .wait() | |
2253 | on a created process from a subthread of the thread that created | |
2254 | the process. | |
2255 | ||
2256 | Usage: | |
2257 | class ProcessXXX(_ThreadFixer, BrokenProcessXXX): | |
2258 | _pclass = BrokenProcessXXX | |
2259 | ||
2260 | Details: | |
2261 | Because we must do all real os.wait() calls on the child | |
2262 | process from the thread that spawned it, we use a proxy | |
2263 | thread whose only responsibility is just that. The proxy | |
2264 | thread just starts the child and then immediately wait's for | |
2265 | the child to terminate. On termination is stores the exit | |
2266 | status (for use by the main thread) and notifies any thread | |
2267 | waiting for this termination (possibly the main thread). The | |
2268 | overriden .wait() uses this stored exit status and the | |
2269 | termination notification to simulate the .wait(). | |
2270 | """ | |
2271 | def __init__(self, *args, **kwargs): | |
2272 | # Keep a reference to 'log' ensure it is around for this object's | |
2273 | # destruction. | |
2274 | self.__log = log | |
2275 | self.__waiter = None | |
2276 | self.__hasTerminated = threading.Condition() | |
2277 | self.__terminationResult = None | |
2278 | self.__childStarted = threading.Condition() | |
2279 | self._pclass.__init__(self, *args, **kwargs) | |
2280 | ||
2281 | def _forkAndExecChildOnUnix(self, *args, **kwargs): | |
2282 | """Fork and start the child process do it in a special subthread | |
2283 | that will negotiate subsequent .wait()'s. | |
2284 | ||
2285 | Sets self._pid as a side effect. | |
2286 | """ | |
2287 | self.__waiter = threading.Thread( | |
2288 | target=self.__launchAndWait, args=args, kwargs=kwargs) | |
2289 | ||
2290 | # Start subthread that will launch child and wait until it | |
2291 | # *has* started. | |
2292 | self.__childStarted.acquire() | |
2293 | self.__waiter.start() | |
2294 | self.__childStarted.wait() | |
2295 | self.__childStarted.release() | |
2296 | ||
2297 | def __launchAndWait(self, *args, **kwargs): | |
2298 | """Launch the given command and wait for it to terminate. | |
2299 | ||
2300 | When the process has terminated then store its exit value | |
2301 | and finish. | |
2302 | """ | |
2303 | logfix.info("start child in thread %s", | |
2304 | threading.currentThread().getName()) | |
2305 | ||
2306 | # Spawn the child process and notify the main thread of | |
2307 | # this. | |
2308 | self.__childStarted.acquire() | |
2309 | self._pclass._forkAndExecChildOnUnix(self, *args, **kwargs) | |
2310 | self.__childStarted.notifyAll() | |
2311 | self.__childStarted.release() | |
2312 | ||
2313 | # Wait on the thread and store appropriate results when | |
2314 | # finished. | |
2315 | try: | |
2316 | waitResult = self._pclass.wait(self) | |
2317 | except ProcessError, ex: | |
2318 | waitResult = ex | |
2319 | self.__hasTerminated.acquire() | |
2320 | self.__terminationResult = waitResult | |
2321 | self.__hasTerminated.notifyAll() | |
2322 | self.__hasTerminated.release() | |
2323 | ||
2324 | self.__waiter = None # drop ref that would keep instance alive | |
2325 | ||
2326 | def wait(self, timeout=None): | |
2327 | # If the process __hasTerminated then return the exit | |
2328 | # status. Otherwise simulate the wait as appropriate. | |
2329 | # Note: | |
2330 | # - This class is only used on linux so 'timeout' has the | |
2331 | # Unix 'timeout' semantics. | |
2332 | self.__hasTerminated.acquire() | |
2333 | if self.__terminationResult is None: | |
2334 | if timeout == os.WNOHANG: # Poll. | |
2335 | self.__hasTerminated.wait(0) | |
2336 | else: # Block until process finishes. | |
2337 | self.__hasTerminated.wait() | |
2338 | terminationResult = self.__terminationResult | |
2339 | self.__hasTerminated.release() | |
2340 | ||
2341 | if terminationResult is None: | |
2342 | # process has not finished yet | |
2343 | raise ProcessError("Wait for process timed out.", | |
2344 | self.WAIT_TIMEOUT) | |
2345 | elif isinstance(terminationResult, Exception): | |
2346 | # some error waiting for process termination | |
2347 | raise terminationResult | |
2348 | else: | |
2349 | # the process terminated | |
2350 | return terminationResult | |
2351 | ||
2352 | _ThreadBrokenProcess = Process | |
2353 | class Process(_ThreadFixer, _ThreadBrokenProcess): | |
2354 | _pclass = _ThreadBrokenProcess | |
2355 | ||
2356 | _ThreadBrokenProcessOpen = ProcessOpen | |
2357 | class ProcessOpen(_ThreadFixer, _ThreadBrokenProcessOpen): | |
2358 | _pclass = _ThreadBrokenProcessOpen | |
2359 | ||
2360 | _ThreadBrokenProcessProxy = ProcessProxy | |
2361 | class ProcessProxy(_ThreadFixer, _ThreadBrokenProcessProxy): | |
2362 | _pclass = _ThreadBrokenProcessProxy | |
2363 | ||
2364 |