Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(287)

Side by Side Diff: third_party/mozprocess/mozprocess/processhandler.py

Issue 108313011: Adding mozilla libraries required by Firefox interop test. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/deps/
Patch Set: Created 7 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « third_party/mozprocess/mozprocess/pid.py ('k') | third_party/mozprocess/mozprocess/qijo.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Property Changes:
Added: svn:eol-style
+ LF
OLDNEW
(Empty)
1 # This Source Code Form is subject to the terms of the Mozilla Public
2 # License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 # You can obtain one at http://mozilla.org/MPL/2.0/.
4
5 import logging
6 import mozinfo
7 import os
8 import select
9 import signal
10 import subprocess
11 import sys
12 import threading
13 import time
14 import traceback
15 from Queue import Queue
16 from datetime import datetime, timedelta
17 __all__ = ['ProcessHandlerMixin', 'ProcessHandler']
18
19 # Set the MOZPROCESS_DEBUG environment variable to 1 to see some debugging outpu t
20 MOZPROCESS_DEBUG = os.getenv("MOZPROCESS_DEBUG")
21
22 if mozinfo.isWin:
23 import ctypes, ctypes.wintypes, msvcrt
24 from ctypes import sizeof, addressof, c_ulong, byref, POINTER, WinError, c_l onglong
25 import winprocess
26 from qijo import JobObjectAssociateCompletionPortInformation,\
27 JOBOBJECT_ASSOCIATE_COMPLETION_PORT, JobObjectExtendedLimitInformation,\
28 JOBOBJECT_BASIC_LIMIT_INFORMATION, JOBOBJECT_EXTENDED_LIMIT_INFORMATION, IO_ COUNTERS
29
30 class ProcessHandlerMixin(object):
31 """Class which represents a process to be executed."""
32
33 class Process(subprocess.Popen):
34 """
35 Represents our view of a subprocess.
36 It adds a kill() method which allows it to be stopped explicitly.
37 """
38
39 MAX_IOCOMPLETION_PORT_NOTIFICATION_DELAY = 180
40 MAX_PROCESS_KILL_DELAY = 30
41
42 def __init__(self,
43 args,
44 bufsize=0,
45 executable=None,
46 stdin=None,
47 stdout=None,
48 stderr=None,
49 preexec_fn=None,
50 close_fds=False,
51 shell=False,
52 cwd=None,
53 env=None,
54 universal_newlines=False,
55 startupinfo=None,
56 creationflags=0,
57 ignore_children=False):
58
59 # Parameter for whether or not we should attempt to track child proc esses
60 self._ignore_children = ignore_children
61
62 if not self._ignore_children and not mozinfo.isWin:
63 # Set the process group id for linux systems
64 # Sets process group id to the pid of the parent process
65 # NOTE: This prevents you from using preexec_fn and managing
66 # child processes, TODO: Ideally, find a way around this
67 def setpgidfn():
68 os.setpgid(0, 0)
69 preexec_fn = setpgidfn
70
71 try:
72 subprocess.Popen.__init__(self, args, bufsize, executable,
73 stdin, stdout, stderr,
74 preexec_fn, close_fds,
75 shell, cwd, env,
76 universal_newlines, startupinfo, creat ionflags)
77 except OSError, e:
78 print >> sys.stderr, args
79 raise
80
81 def __del__(self, _maxint=sys.maxint):
82 if mozinfo.isWin:
83 if self._handle:
84 if hasattr(self, '_internal_poll'):
85 self._internal_poll(_deadstate=_maxint)
86 else:
87 self.poll(_deadstate=sys.maxint)
88 if self._handle or self._job or self._io_port:
89 self._cleanup()
90 else:
91 subprocess.Popen.__del__(self)
92
93 def kill(self):
94 self.returncode = 0
95 if mozinfo.isWin:
96 if not self._ignore_children and self._handle and self._job:
97 winprocess.TerminateJobObject(self._job, winprocess.ERROR_CO NTROL_C_EXIT)
98 self.returncode = winprocess.GetExitCodeProcess(self._handle )
99 elif self._handle:
100 err = None
101 try:
102 winprocess.TerminateProcess(self._handle, winprocess.ERR OR_CONTROL_C_EXIT)
103 except:
104 err = "Could not terminate process"
105 self.returncode = winprocess.GetExitCodeProcess(self._handle )
106 self._cleanup()
107 if err is not None:
108 raise OSError(err)
109 else:
110 pass
111 else:
112 if not self._ignore_children:
113 try:
114 os.killpg(self.pid, signal.SIGKILL)
115 except BaseException, e:
116 if getattr(e, "errno", None) != 3:
117 # Error 3 is "no such process", which is ok
118 print >> sys.stdout, "Could not kill process, could not find pid: %s, assuming it's already dead" % self.pid
119 else:
120 os.kill(self.pid, signal.SIGKILL)
121 if self.returncode is None:
122 self.returncode = subprocess.Popen._internal_poll(self)
123
124 self._cleanup()
125 return self.returncode
126
127 def wait(self):
128 """ Popen.wait
129 Called to wait for a running process to shut down and return
130 its exit code
131 Returns the main process's exit code
132 """
133 # This call will be different for each OS
134 self.returncode = self._wait()
135 self._cleanup()
136 return self.returncode
137
138 """ Private Members of Process class """
139
140 if mozinfo.isWin:
141 # Redefine the execute child so that we can track process groups
142 def _execute_child(self, args, executable, preexec_fn, close_fds,
143 cwd, env, universal_newlines, startupinfo,
144 creationflags, shell,
145 p2cread, p2cwrite,
146 c2pread, c2pwrite,
147 errread, errwrite):
148 if not isinstance(args, basestring):
149 args = subprocess.list2cmdline(args)
150
151 # Always or in the create new process group
152 creationflags |= winprocess.CREATE_NEW_PROCESS_GROUP
153
154 if startupinfo is None:
155 startupinfo = winprocess.STARTUPINFO()
156
157 if None not in (p2cread, c2pwrite, errwrite):
158 startupinfo.dwFlags |= winprocess.STARTF_USESTDHANDLES
159 startupinfo.hStdInput = int(p2cread)
160 startupinfo.hStdOutput = int(c2pwrite)
161 startupinfo.hStdError = int(errwrite)
162 if shell:
163 startupinfo.dwFlags |= winprocess.STARTF_USESHOWWINDOW
164 startupinfo.wShowWindow = winprocess.SW_HIDE
165 comspec = os.environ.get("COMSPEC", "cmd.exe")
166 args = comspec + " /c " + args
167
168 # determine if we can create create a job
169 canCreateJob = winprocess.CanCreateJobObject()
170
171 # Ensure we write a warning message if we are falling back
172 if not canCreateJob and not self._ignore_children:
173 # We can't create job objects AND the user wanted us to
174 # Warn the user about this.
175 print >> sys.stderr, "ProcessManager UNABLE to use job objec ts to manage child processes"
176
177 # set process creation flags
178 creationflags |= winprocess.CREATE_SUSPENDED
179 creationflags |= winprocess.CREATE_UNICODE_ENVIRONMENT
180 if canCreateJob:
181 creationflags |= winprocess.CREATE_BREAKAWAY_FROM_JOB
182 else:
183 # Since we've warned, we just log info here to inform you
184 # of the consequence of setting ignore_children = True
185 print "ProcessManager NOT managing child processes"
186
187 # create the process
188 hp, ht, pid, tid = winprocess.CreateProcess(
189 executable, args,
190 None, None, # No special security
191 1, # Must inherit handles!
192 creationflags,
193 winprocess.EnvironmentBlock(env),
194 cwd, startupinfo)
195 self._child_created = True
196 self._handle = hp
197 self._thread = ht
198 self.pid = pid
199 self.tid = tid
200
201 if not self._ignore_children and canCreateJob:
202 try:
203 # We create a new job for this process, so that we can k ill
204 # the process and any sub-processes
205 # Create the IO Completion Port
206 self._io_port = winprocess.CreateIoCompletionPort()
207 self._job = winprocess.CreateJobObject()
208
209 # Now associate the io comp port and the job object
210 joacp = JOBOBJECT_ASSOCIATE_COMPLETION_PORT(winprocess.C OMPKEY_JOBOBJECT,
211 self._io_por t)
212 winprocess.SetInformationJobObject(self._job,
213 JobObjectAssociateComp letionPortInformation,
214 addressof(joacp),
215 sizeof(joacp)
216 )
217
218 # Allow subprocesses to break away from us - necessary f or
219 # flash with protected mode
220 jbli = JOBOBJECT_BASIC_LIMIT_INFORMATION(
221 c_longlong(0), # per process tim e limit (ignored)
222 c_longlong(0), # per job user ti me limit (ignored)
223 winprocess.JOB_OBJECT_LIMIT_BREA KAWAY_OK,
224 0, # min working set (ignored)
225 0, # max working set (ignored)
226 0, # active process limit (ignor ed)
227 None, # affinity (ignored)
228 0, # Priority class (ignored)
229 0, # Scheduling class (ignored)
230 )
231
232 iocntr = IO_COUNTERS()
233 jeli = JOBOBJECT_EXTENDED_LIMIT_INFORMATION(
234 jbli, # basic limit info struct
235 iocntr, # io_counters (ignore d)
236 0, # process mem limit (ignor ed)
237 0, # job mem limit (ignored)
238 0, # peak process limit (igno red)
239 0) # peak job limit (ignored)
240
241 winprocess.SetInformationJobObject(self._job,
242 JobObjectExtendedLimi tInformation,
243 addressof(jeli),
244 sizeof(jeli)
245 )
246
247 # Assign the job object to the process
248 winprocess.AssignProcessToJobObject(self._job, int(hp))
249
250 # It's overkill, but we use Queue to signal between thre ads
251 # because it handles errors more gracefully than event o r condition.
252 self._process_events = Queue()
253
254 # Spin up our thread for managing the IO Completion Port
255 self._procmgrthread = threading.Thread(target = self._pr ocmgr)
256 except:
257 print >> sys.stderr, """Exception trying to use job obje cts;
258 falling back to not using job objects for managing child processes"""
259 tb = traceback.format_exc()
260 print >> sys.stderr, tb
261 # Ensure no dangling handles left behind
262 self._cleanup_job_io_port()
263 else:
264 self._job = None
265
266 winprocess.ResumeThread(int(ht))
267 if getattr(self, '_procmgrthread', None):
268 self._procmgrthread.start()
269 ht.Close()
270
271 for i in (p2cread, c2pwrite, errwrite):
272 if i is not None:
273 i.Close()
274
275 # Windows Process Manager - watches the IO Completion Port and
276 # keeps track of child processes
277 def _procmgr(self):
278 if not (self._io_port) or not (self._job):
279 return
280
281 try:
282 self._poll_iocompletion_port()
283 except KeyboardInterrupt:
284 raise KeyboardInterrupt
285
286 def _poll_iocompletion_port(self):
287 # Watch the IO Completion port for status
288 self._spawned_procs = {}
289 countdowntokill = 0
290
291 if MOZPROCESS_DEBUG:
292 print "DBG::MOZPROC Self.pid value is: %s" % self.pid
293
294 while True:
295 msgid = c_ulong(0)
296 compkey = c_ulong(0)
297 pid = c_ulong(0)
298 portstatus = winprocess.GetQueuedCompletionStatus(self._io_p ort,
299 byref(msgi d),
300 byref(comp key),
301 byref(pid) ,
302 5000)
303
304 # If the countdowntokill has been activated, we need to chec k
305 # if we should start killing the children or not.
306 if countdowntokill != 0:
307 diff = datetime.now() - countdowntokill
308 # Arbitrarily wait 3 minutes for windows to get its act together
309 # Windows sometimes takes a small nap between notifying the
310 # IO Completion port and actually killing the children, and we
311 # don't want to mistake that situation for the situation of an unexpected
312 # parent abort (which is what we're looking for here).
313 if diff.seconds > self.MAX_IOCOMPLETION_PORT_NOTIFICATIO N_DELAY:
314 print >> sys.stderr, "Parent process %s exited with children alive:" % self.pid
315 print >> sys.stderr, "PIDS: %s" % ', '.join([str(i) for i in self._spawned_procs])
316 print >> sys.stderr, "Attempting to kill them..."
317 self.kill()
318 self._process_events.put({self.pid: 'FINISHED'})
319
320 if not portstatus:
321 # Check to see what happened
322 errcode = winprocess.GetLastError()
323 if errcode == winprocess.ERROR_ABANDONED_WAIT_0:
324 # Then something has killed the port, break the loop
325 print >> sys.stderr, "IO Completion Port unexpectedl y closed"
326 break
327 elif errcode == winprocess.WAIT_TIMEOUT:
328 # Timeouts are expected, just keep on polling
329 continue
330 else:
331 print >> sys.stderr, "Error Code %s trying to query IO Completion Port, exiting" % errcode
332 raise WinError(errcode)
333 break
334
335 if compkey.value == winprocess.COMPKEY_TERMINATE.value:
336 if MOZPROCESS_DEBUG:
337 print "DBG::MOZPROC compkeyterminate detected"
338 # Then we're done
339 break
340
341 # Check the status of the IO Port and do things based on it
342 if compkey.value == winprocess.COMPKEY_JOBOBJECT.value:
343 if msgid.value == winprocess.JOB_OBJECT_MSG_ACTIVE_PROCE SS_ZERO:
344 # No processes left, time to shut down
345 # Signal anyone waiting on us that it is safe to shu t down
346 if MOZPROCESS_DEBUG:
347 print "DBG::MOZPROC job object msg active proces ses zero"
348 self._process_events.put({self.pid: 'FINISHED'})
349 break
350 elif msgid.value == winprocess.JOB_OBJECT_MSG_NEW_PROCES S:
351 # New Process started
352 # Add the child proc to our list in case our parent flakes out on us
353 # without killing everything.
354 if pid.value != self.pid:
355 self._spawned_procs[pid.value] = 1
356 if MOZPROCESS_DEBUG:
357 print "DBG::MOZPROC new process detected wit h pid value: %s" % pid.value
358 elif msgid.value == winprocess.JOB_OBJECT_MSG_EXIT_PROCE SS:
359 if MOZPROCESS_DEBUG:
360 print "DBG::MOZPROC process id %s exited normall y" % pid.value
361 # One process exited normally
362 if pid.value == self.pid and len(self._spawned_procs ) > 0:
363 # Parent process dying, start countdown timer
364 countdowntokill = datetime.now()
365 elif pid.value in self._spawned_procs:
366 # Child Process died remove from list
367 del(self._spawned_procs[pid.value])
368 elif msgid.value == winprocess.JOB_OBJECT_MSG_ABNORMAL_E XIT_PROCESS:
369 # One process existed abnormally
370 if MOZPROCESS_DEBUG:
371 print "DBG::MOZPROC process id %s existed abnorm ally" % pid.value
372 if pid.value == self.pid and len(self._spawned_procs ) > 0:
373 # Parent process dying, start countdown timer
374 countdowntokill = datetime.now()
375 elif pid.value in self._spawned_procs:
376 # Child Process died remove from list
377 del self._spawned_procs[pid.value]
378 else:
379 # We don't care about anything else
380 if MOZPROCESS_DEBUG:
381 print "DBG::MOZPROC We got a message %s" % msgid .value
382 pass
383
384 def _wait(self):
385
386 # First, check to see if the process is still running
387 if self._handle:
388 self.returncode = winprocess.GetExitCodeProcess(self._handle )
389 else:
390 # Dude, the process is like totally dead!
391 return self.returncode
392
393 # Python 2.5 uses isAlive versus is_alive use the proper one
394 threadalive = False
395 if hasattr(self, "_procmgrthread"):
396 if hasattr(self._procmgrthread, 'is_alive'):
397 threadalive = self._procmgrthread.is_alive()
398 else:
399 threadalive = self._procmgrthread.isAlive()
400 if self._job and threadalive:
401 # Then we are managing with IO Completion Ports
402 # wait on a signal so we know when we have seen the last
403 # process come through.
404 # We use queues to synchronize between the thread and this
405 # function because events just didn't have robust enough err or
406 # handling on pre-2.7 versions
407 err = None
408 try:
409 # timeout is the max amount of time the procmgr thread w ill wait for
410 # child processes to shutdown before killing them with e xtreme prejudice.
411 item = self._process_events.get(timeout=self.MAX_IOCOMPL ETION_PORT_NOTIFICATION_DELAY +
412 self.MAX_PROCESS _KILL_DELAY)
413 if item[self.pid] == 'FINISHED':
414 self._process_events.task_done()
415 except:
416 err = "IO Completion Port failed to signal process shutd own"
417 # Either way, let's try to get this code
418 if self._handle:
419 self.returncode = winprocess.GetExitCodeProcess(self._ha ndle)
420 self._cleanup()
421
422 if err is not None:
423 raise OSError(err)
424
425
426 else:
427 # Not managing with job objects, so all we can reasonably do
428 # is call waitforsingleobject and hope for the best
429
430 if MOZPROCESS_DEBUG and not self._ignore_children:
431 print "DBG::MOZPROC NOT USING JOB OBJECTS!!!"
432 # First, make sure we have not already ended
433 if self.returncode != winprocess.STILL_ACTIVE:
434 self._cleanup()
435 return self.returncode
436
437 rc = None
438 if self._handle:
439 rc = winprocess.WaitForSingleObject(self._handle, -1)
440
441 if rc == winprocess.WAIT_TIMEOUT:
442 # The process isn't dead, so kill it
443 print "Timed out waiting for process to close, attemptin g TerminateProcess"
444 self.kill()
445 elif rc == winprocess.WAIT_OBJECT_0:
446 # We caught WAIT_OBJECT_0, which indicates all is well
447 print "Single process terminated successfully"
448 self.returncode = winprocess.GetExitCodeProcess(self._ha ndle)
449 else:
450 # An error occured we should probably throw
451 rc = winprocess.GetLastError()
452 if rc:
453 raise WinError(rc)
454
455 self._cleanup()
456
457 return self.returncode
458
459 def _cleanup_job_io_port(self):
460 """ Do the job and IO port cleanup separately because there are
461 cases where we want to clean these without killing _handle
462 (i.e. if we fail to create the job object in the first place )
463 """
464 if getattr(self, '_job') and self._job != winprocess.INVALID_HAN DLE_VALUE:
465 self._job.Close()
466 self._job = None
467 else:
468 # If windows already freed our handle just set it to none
469 # (saw this intermittently while testing)
470 self._job = None
471
472 if getattr(self, '_io_port', None) and self._io_port != winproce ss.INVALID_HANDLE_VALUE:
473 self._io_port.Close()
474 self._io_port = None
475 else:
476 self._io_port = None
477
478 if getattr(self, '_procmgrthread', None):
479 self._procmgrthread = None
480
481 def _cleanup(self):
482 self._cleanup_job_io_port()
483 if self._thread and self._thread != winprocess.INVALID_HANDLE_VA LUE:
484 self._thread.Close()
485 self._thread = None
486 else:
487 self._thread = None
488
489 if self._handle and self._handle != winprocess.INVALID_HANDLE_VA LUE:
490 self._handle.Close()
491 self._handle = None
492 else:
493 self._handle = None
494
495 elif mozinfo.isMac or mozinfo.isUnix:
496
497 def _wait(self):
498 """ Haven't found any reason to differentiate between these plat forms
499 so they all use the same wait callback. If it is necessary to
500 craft different styles of wait, then a new _wait method
501 could be easily implemented.
502 """
503
504 if not self._ignore_children:
505 try:
506 # os.waitpid returns a (pid, status) tuple
507 return os.waitpid(self.pid, 0)[1]
508 except OSError, e:
509 if getattr(e, "errno", None) != 10:
510 # Error 10 is "no child process", which could indica te normal
511 # close
512 print >> sys.stderr, "Encountered error waiting for pid to close: %s" % e
513 raise
514 return 0
515
516 else:
517 # For non-group wait, call base class
518 subprocess.Popen.wait(self)
519 return self.returncode
520
521 def _cleanup(self):
522 pass
523
524 else:
525 # An unrecognized platform, we will call the base class for everythi ng
526 print >> sys.stderr, "Unrecognized platform, process groups may not be managed properly"
527
528 def _wait(self):
529 self.returncode = subprocess.Popen.wait(self)
530 return self.returncode
531
532 def _cleanup(self):
533 pass
534
535 def __init__(self,
536 cmd,
537 args=None,
538 cwd=None,
539 env=None,
540 ignore_children = False,
541 processOutputLine=(),
542 onTimeout=(),
543 onFinish=(),
544 **kwargs):
545 """
546 cmd = Command to run
547 args = array of arguments (defaults to None)
548 cwd = working directory for cmd (defaults to None)
549 env = environment to use for the process (defaults to os.environ)
550 ignore_children = when True, causes system to ignore child processes,
551 defaults to False (which tracks child processes)
552 processOutputLine = handlers to process the output line
553 onTimeout = handlers for timeout event
554 kwargs = keyword args to pass directly into Popen
555
556 NOTE: Child processes will be tracked by default. If for any reason
557 we are unable to track child processes and ignore_children is set to Fal se,
558 then we will fall back to only tracking the root process. The fallback
559 will be logged.
560 """
561 self.cmd = cmd
562 self.args = args
563 self.cwd = cwd
564 self.didTimeout = False
565 self._ignore_children = ignore_children
566 self.keywordargs = kwargs
567 self.outThread = None
568
569 if env is None:
570 env = os.environ.copy()
571 self.env = env
572
573 # handlers
574 self.processOutputLineHandlers = list(processOutputLine)
575 self.onTimeoutHandlers = list(onTimeout)
576 self.onFinishHandlers = list(onFinish)
577
578 # It is common for people to pass in the entire array with the cmd and
579 # the args together since this is how Popen uses it. Allow for that.
580 if not isinstance(self.cmd, list):
581 self.cmd = [self.cmd]
582
583 if self.args:
584 self.cmd = self.cmd + self.args
585
586 @property
587 def timedOut(self):
588 """True if the process has timed out."""
589 return self.didTimeout
590
591 @property
592 def commandline(self):
593 """the string value of the command line"""
594 return subprocess.list2cmdline([self.cmd] + self.args)
595
596 def run(self, timeout=None, outputTimeout=None):
597 """
598 Starts the process.
599
600 If timeout is not None, the process will be allowed to continue for
601 that number of seconds before being killed.
602
603 If outputTimeout is not None, the process will be allowed to continue
604 for that number of seconds without producing any output before
605 being killed.
606 """
607 self.didTimeout = False
608 self.startTime = datetime.now()
609
610 # default arguments
611 args = dict(stdout=subprocess.PIPE,
612 stderr=subprocess.STDOUT,
613 cwd=self.cwd,
614 env=self.env,
615 ignore_children=self._ignore_children)
616
617 # build process arguments
618 args.update(self.keywordargs)
619
620 # launch the process
621 self.proc = self.Process(self.cmd, **args)
622
623 self.processOutput(timeout=timeout, outputTimeout=outputTimeout)
624
625 def kill(self):
626 """
627 Kills the managed process and if you created the process with
628 'ignore_children=False' (the default) then it will also
629 also kill all child processes spawned by it.
630 If you specified 'ignore_children=True' when creating the process,
631 only the root process will be killed.
632
633 Note that this does not manage any state, save any output etc,
634 it immediately kills the process.
635 """
636 return self.proc.kill()
637
638 def readWithTimeout(self, f, timeout):
639 """
640 Try to read a line of output from the file object |f|.
641 |f| must be a pipe, like the |stdout| member of a subprocess.Popen
642 object created with stdout=PIPE. If no output
643 is received within |timeout| seconds, return a blank line.
644 Returns a tuple (line, did_timeout), where |did_timeout| is True
645 if the read timed out, and False otherwise.
646
647 Calls a private member because this is a different function based on
648 the OS
649 """
650 return self._readWithTimeout(f, timeout)
651
652 def processOutputLine(self, line):
653 """Called for each line of output that a process sends to stdout/stderr.
654 """
655 for handler in self.processOutputLineHandlers:
656 handler(line)
657
658 def onTimeout(self):
659 """Called when a process times out."""
660 for handler in self.onTimeoutHandlers:
661 handler()
662
663 def onFinish(self):
664 """Called when a process finishes without a timeout."""
665 for handler in self.onFinishHandlers:
666 handler()
667
668 def processOutput(self, timeout=None, outputTimeout=None):
669 """
670 Handle process output until the process terminates or times out.
671
672 If timeout is not None, the process will be allowed to continue for
673 that number of seconds before being killed.
674
675 If outputTimeout is not None, the process will be allowed to continue
676 for that number of seconds without producing any output before
677 being killed.
678 """
679 def _processOutput():
680 self.didTimeout = False
681 logsource = self.proc.stdout
682
683 lineReadTimeout = None
684 if timeout:
685 lineReadTimeout = timeout - (datetime.now() - self.startTime).se conds
686 elif outputTimeout:
687 lineReadTimeout = outputTimeout
688
689 (line, self.didTimeout) = self.readWithTimeout(logsource, lineReadTi meout)
690 while line != "" and not self.didTimeout:
691 self.processOutputLine(line.rstrip())
692 if timeout:
693 lineReadTimeout = timeout - (datetime.now() - self.startTime ).seconds
694 (line, self.didTimeout) = self.readWithTimeout(logsource, lineRe adTimeout)
695
696 if self.didTimeout:
697 self.proc.kill()
698 self.onTimeout()
699 else:
700 self.onFinish()
701
702 if not hasattr(self, 'proc'):
703 self.run()
704
705 if not self.outThread:
706 self.outThread = threading.Thread(target=_processOutput)
707 self.outThread.daemon = True
708 self.outThread.start()
709
710
711 def wait(self, timeout=None):
712 """
713 Waits until all output has been read and the process is
714 terminated.
715
716 If timeout is not None, will return after timeout seconds.
717 This timeout only causes the wait function to return and
718 does not kill the process.
719 """
720 if self.outThread:
721 # Thread.join() blocks the main thread until outThread is finished
722 # wake up once a second in case a keyboard interrupt is sent
723 count = 0
724 while self.outThread.isAlive():
725 self.outThread.join(timeout=1)
726 count += 1
727 if timeout and count > timeout:
728 return
729
730 return self.proc.wait()
731
732 # TODO Remove this method when consumers have been fixed
733 def waitForFinish(self, timeout=None):
734 print >> sys.stderr, "MOZPROCESS WARNING: ProcessHandler.waitForFinish() is deprecated, " \
735 "use ProcessHandler.wait() instead"
736 return self.wait(timeout=timeout)
737
738
739 ### Private methods from here on down. Thar be dragons.
740
741 if mozinfo.isWin:
742 # Windows Specific private functions are defined in this block
743 PeekNamedPipe = ctypes.windll.kernel32.PeekNamedPipe
744 GetLastError = ctypes.windll.kernel32.GetLastError
745
746 def _readWithTimeout(self, f, timeout):
747 if timeout is None:
748 # shortcut to allow callers to pass in "None" for no timeout.
749 return (f.readline(), False)
750 x = msvcrt.get_osfhandle(f.fileno())
751 l = ctypes.c_long()
752 done = time.time() + timeout
753 while time.time() < done:
754 if self.PeekNamedPipe(x, None, 0, None, ctypes.byref(l), None) = = 0:
755 err = self.GetLastError()
756 if err == 38 or err == 109: # ERROR_HANDLE_EOF || ERROR_BROK EN_PIPE
757 return ('', False)
758 else:
759 raise OSError("readWithTimeout got error: %d", err)
760 if l.value > 0:
761 # we're assuming that the output is line-buffered,
762 # which is not unreasonable
763 return (f.readline(), False)
764 time.sleep(0.01)
765 return ('', True)
766
767 else:
768 # Generic
769 def _readWithTimeout(self, f, timeout):
770 try:
771 (r, w, e) = select.select([f], [], [], timeout)
772 except:
773 # return a blank line
774 return ('', True)
775
776 if len(r) == 0:
777 return ('', True)
778 return (f.readline(), False)
779
780 @property
781 def pid(self):
782 return self.proc.pid
783
784
785 ### default output handlers
786 ### these should be callables that take the output line
787
788 def print_output(line):
789 print line
790
791 class StoreOutput(object):
792 """accumulate stdout"""
793
794 def __init__(self):
795 self.output = []
796
797 def __call__(self, line):
798 self.output.append(line)
799
800 class LogOutput(object):
801 """pass output to a file"""
802
803 def __init__(self, filename):
804 self.filename = filename
805 self.file = None
806
807 def __call__(self, line):
808 if self.file is None:
809 self.file = file(self.filename, 'a')
810 self.file.write(line + '\n')
811 self.file.flush()
812
813 def __del__(self):
814 if self.file is not None:
815 self.file.close()
816
817 ### front end class with the default handlers
818
819 class ProcessHandler(ProcessHandlerMixin):
820
821 def __init__(self, cmd, logfile=None, storeOutput=True, **kwargs):
822 """
823 If storeOutput=True, the output produced by the process will be saved
824 as self.output.
825
826 If logfile is not None, the output produced by the process will be
827 appended to the given file.
828 """
829
830 kwargs.setdefault('processOutputLine', [])
831
832 # Print to standard output only if no outputline provided
833 if not kwargs['processOutputLine']:
834 kwargs['processOutputLine'].append(print_output)
835
836 if logfile:
837 logoutput = LogOutput(logfile)
838 kwargs['processOutputLine'].append(logoutput)
839
840 self.output = None
841 if storeOutput:
842 storeoutput = StoreOutput()
843 self.output = storeoutput.output
844 kwargs['processOutputLine'].append(storeoutput)
845
846 ProcessHandlerMixin.__init__(self, cmd, **kwargs)
OLDNEW
« no previous file with comments | « third_party/mozprocess/mozprocess/pid.py ('k') | third_party/mozprocess/mozprocess/qijo.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698