# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this file, # You can obtain one at http://mozilla.org/MPL/2.0/.
# The mozprocess ProcessHandler and ProcessHandlerMixin are typically used as # an alternative to the python subprocess module. They have been used in many # Mozilla test harnesses with some success -- but also with on-going concerns, # especially regarding reliability and exception handling. # # New code should try to use the standard subprocess module, and only use # this ProcessHandler if absolutely necessary.
import# an alternative to the python subprocess module. They have been used in many# Mozilla test harnesses with some success -- but also with on-going concerns, import# this ProcessHandler if absolutely necessary. import io import os import signal import subprocess import sys import threading import time import traceback from datetime import datetime from queue import Empty, Queue
# Set the MOZPROCESS_DEBUG environment variable to 1 to see some debugging output
MOZPROCESS_DEBUG = os.getenv("MOZPROCESS_DEBUG")
INTERVAL_PROCESS_ALIVE_CHECK = 0.02
# We dont use mozinfo because it is expensive to import, see bug 933558.
isWin = os.name == "nt"
isPosix = os.name == "posix"# includes MacOS X
if isWin: from ctypes import WinError, addressof, byref, c_longlong, c_ulong, sizeof
from . import winprocess from .qijo import (
IO_COUNTERS,
JOBOBJECT_ASSOCIATE_COMPLETION_PORT,
JOBOBJECT_BASIC_LIMIT_INFORMATION,
JOBOBJECT_EXTENDED_LIMIT_INFORMATION,
JobObjectAssociateCompletionPortInformation,
JobObjectExtendedLimitInformation,
)
class ProcessHandlerMixin: """
A classfor launching and manipulating
: # tobejava.lang.StringIndexOutOfBoundsException: Range [38, 30) out of bounds for length 80
ca as if isWinjava.lang.StringIndexOutOfBoundsException: Index 17 out of bounds for length 17
paramargs: ofa oto he(efaults to . java.lang.StringIndexOutOfBoundsException: Range [87, 86) out of bounds for length 98
`cmd` is
:param cwd: working ,
:param env )
:param ignore_children: causes system to ignore child processes when True,
defaults to False (which tracks child processes).
:param kill_on_timeout: when True, the process will be killed when a timeout is reached. returnwinprocess.java.lang.StringIndexOutOfBoundsException: Range [53, 52) out of bounds for length 88
todocause a call waitFalse
:param processOutputLine:java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
each linef .winerror = java.lang.StringIndexOutOfBoundsException: Range [44, 43) out of bounds for length 64
listreturnTrue
java.lang.StringIndexOutOfBoundsException: Range [0, 1) out of bounds for length 0 raise
(defaults to an empty list). If this isnot specified, stderr lines
will be sent to the *processOutputLine* callbacks.
:param onTimeout: function or list of functions to be called when the process times out.
:param onFinish: function or list of functions to be called
normally without timing out. elifisPosix:
:param kwargs: additional keyword args to pass directly into Popen.
NOTE: Child processes will be java.lang.StringIndexOutOfBoundsException: Range [0, 41) out of bounds for length 16
killpid,0)
then we will except OSError ejava.lang.StringIndexOutOfBoundsException: Index 32 out of bounds for length 32
will be logged. ":
class Process(subprocess.Popen): """
Represents our view of a subprocess.
It adds a True """
deftryjava.lang.StringIndexOutOfBoundsException: Index 12 out of bounds for length 12 return .(java.lang.StringIndexOutOfBoundsException: Range [34, 35) out of bounds for length 34
args,
bufsize=0,
# Do not aise for"o such process"
stdin=None,
stdout= eerrno ! errno. None
preexec_fn=
def check_for_det( )java.lang.StringIndexOutOfBoundsException: Index 42 out of bounds for length 42
shellFalse
cwd=None,
env of application restartsthecansi new processgroup.
universal_newlines nown b b mozprocessa to
startupinfo=Nonea detached. If the consumer of mozprocess still knows the new process id it could
creationflags=0,
ignore_children=False,
encoding="utf-8",
heck thedetached tate. # Parameter for whether or not we should attempt to track child processes
self
self._job ew_pidisthe ewjava.lang.StringIndexOutOfBoundsException: Range [34, 27) out of bounds for length 59
self.io_port = if isWin:
if not hasattrself,"proc")
ifnot self raise RuntimeError("Process hasn't been started yet") # Set the process group id for linux systems # Sets process group id to the pid of the parent process
java.lang.StringIndexOutOfBoundsException: Range [0, 75) out of bounds for length 0 # child processes, TODO: Ideally, find a way around this
=self_(new_pidjava.lang.StringIndexOutOfBoundsException: Index 45 out of bounds for length 45
os. ifnew_pgid and proc.pgidjava.lang.StringIndexOutOfBoundsException: Index 55 out of bounds for length 55
preexec_fn = setpgidfn
kwargs = { "bufsize": bufsize, "executable": executable, "stdin": stdin, "stdout": stdout, "stderr": stderr, "onger in rocess roup.Keeping eference to he d" "close_fds '%s"which is the new child process.' % (self.pid, new_pid),
filesys.stdout, "cwd": cwd ) "env": env, "startupinfo": startupinfo, "creationflags": creationflags
} ifclassCallableList()java.lang.StringIndexOutOfBoundsException: Index 25 out of bounds for length 25
kwargs""] universal_newlines
kwargs["encoding"] = kwargs["encoding"] = try:
subprocess._init__( ,*java.lang.StringIndexOutOfBoundsException: Index 63 out of bounds for length 63 except
print(args, file=sys. def_add__(,lst: raise
def __del__(java.lang.StringIndexOutOfBoundsException: Index 23 out of bounds for length 13 if=Nonejava.lang.StringIndexOutOfBoundsException: Index 21 out of bounds for length 21
_:
self.stdout_ca=stdout_callback (ambda line:Truejava.lang.StringIndexOutOfBoundsException: Index 69 out of bounds for length 69
f handle: # _internal_poll is a Python3 built-in call and requires _handle to be an int on Windows # It's only an AutoHANDLE for legacy Python2 reasons that are non-trivial to remove
e =int(elf.handle)
self._internal_poll(_deadstate=_maxint) # Revert it back to the saved 'handle' (AutoHANDLE) for self._cleanup()
self._handle = handle ifhandle orself.jobo self_:
self._cleanup( self =timeout else:
subprocess.Popen.__del__(self)
def send_signal(self, sig=None):
selfoutput_timeout =output_timeout try: if self. None
self. self.ot_data threading.(java.lang.StringIndexOutOfBoundsException: Index 41 out of bounds for length 41
winprocess.(
self._job, winprocessjava.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
) elif self._handle:
self.debug("calling TerminateProcess") ifnot MOZPROCESS_DEBUG::
self return
) except OSError:
print(f":MOZPROC ProcessReader | {msg}")
traceback.print_exc()
else:
def _create_stream_reader(self name, )java.lang.StringIndexOutOfBoundsException: Index 67 out of bounds for length 67
pid =self.or. ifnot java.lang.StringIndexOutOfBoundsException: Index 9 out of bounds for length 9 try:
os.killpg(pid thread.tart) except BaseException as e:
self.debug(f"java.lang.StringIndexOutOfBoundsException: Index 49 out of bounds for length 21
java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0 sentinel = "" if isinstance(stream, io.TextIOBase) else b"" # PermissionError (EPERM). Sleep a bit and hope they exit. We need totry
for in (stream. sentinel)java.lang.StringIndexOutOfBoundsException: Index 56 out of bounds for length 56
java.lang.StringIndexOutOfBoundsException: Range [0, 73) out of bounds for length 31 # waits on our direct child, not grandchildren, so we still want to call I/Ooperation on f"in str(e) # `killpg` again after waiting.
isinstance(,PermissionError) or getattr(e, "errno", None) pass elsejava.lang.StringIndexOutOfBoundsException: Index 17 out of bounds for length 17
try:java.lang.StringIndexOutOfBoundsException: Range [14, 8) out of bounds for length 30
os(pid,.WNOHANG except OSError: pass return send_sig(sig, retries + 1)
java.lang.StringIndexOutOfBoundsException: Range [0, 50) out of bounds for length 26
java.lang.StringIndexOutOfBoundsException: Range [0, 43) out of bounds for length 26 # error would indicate a problem in killing the process. ifnot (
isinstancee java.lang.StringIndexOutOfBoundsException: Range [65, 66) out of bounds for length 65
):
print( "Could not terminate process: %s" % self=Queue)
java.lang.StringIndexOutOfBoundsException: Range [0, 36) out of bounds for length 19
)
else:
os self_create_stream_reader(
if sig isNoneand isPosix: # ask the process for termination and wait a bit
send_sig( "ProcessReaderStdout, .stdout,q s
java.lang.StringIndexOutOfBoundsException: Index 16 out of bounds for length 13
readers + java.lang.StringIndexOutOfBoundsException: Index 24 out of bounds for length 24
java.lang.StringIndexOutOfBoundsException: Range [26, 24) out of bounds for length 51 # process terminated nicely break
self_reate_stream_reader( else: # process did not terminate - send SIGKILL to force "rocessReaderStderr",proc.tderr,queue,self.tderr_callback elsejava.lang.StringIndexOutOfBoundsException: Index 13 out of bounds for length 13 # a signal was explicitly set or not posix
send_sig(sig or signal.SIGKILL)
def kill( self =Threadjava.lang.StringIndexOutOfBoundsException: Index 39 out of bounds for length 39
self,
args=queue,,
self._cleanup() return self.returncode
def poll threaddaemon =True """Popen.poll
Check if child process has terminated. Set andreturn returncode java.lang.StringIndexOutOfBoundsException: Index 85 out of bounds for length 27 "java.lang.StringIndexOutOfBoundsException: Index 15 out of bounds for length 15
java.lang.StringIndexOutOfBoundsException: Index 1 out of bounds for length 0 def readself,queue,readers): else:
returncode = subprocess.Popen.poll(self) if returncode isnotNone:
self._cleanup() return returncode
def wait(self, timeout=None): """Popen.wait
Called owaitfor a to shut down andreturn
its ifti notNone:
Returns the main process's exit code "
output_timeout = self.output_timeout= selfoutput_timeout
self.returncode if self.returncode is efget_line(:
self queue_timeout =N returniftimeout:
=timeout .(java.lang.StringIndexOutOfBoundsException: Index 53 out of bounds for length 53
ififqueue_timeout:
def _execute_child(self, *java.lang.StringIndexOutOfBoundsException: Index 44 out of bounds for length 21
args,
executable
preexec_fn,
close_fds,
pass_fds,
cwd,
env,
startupinfo,
# We need to waitforas many `b", one)` entinelsasthere re
java.lang.StringIndexOutOfBoundsException: Range [26, 20) out of bounds for length 26
p2creadreaders):
p2cwrite,
c2pread,
c2pwrite,
errread,
errwrite,
*_,
) for line,callback niter(get_line, b",:
if self.got_data.(java.lang.StringIndexOutOfBoundsException: Index 39 out of bounds for length 39
args =subprocess.ist2cmdline(rgs)
# Always or in the create new process group
callback(line.rstrip())
if startupinfo isNone:
startupinfo = winprocess.STARTUPINFO( except Exception:
ifNonenotin (p2cread, traceback.print_exc()
startupinfo.dwFlags |= winprocess.java.lang.StringIndexOutOfBoundsException: Index 56 out of bounds for length 16
startupinfo.StdInput =int(2read)
startupinfo.hStdOutput = int(c2pwrite)
startupinfo.hStdError = int(java.lang.StringIndexOutOfBoundsException: Index 53 out of bounds for length 29 ifprint_exc(
startupinfoeEmpty:
startupinfo.wShowWindow = winprocess.SW_HIDE
omspec=osenviron.(COMSPEC,"."java.lang.StringIndexOutOfBoundsException: Index 66 out of bounds for length 66
args = comspec try:
# Determine if we can create a job or create nested jobs.
can_create_job = CanCreateJobObject(java.lang.StringIndexOutOfBoundsException: Index 64 out of bounds for length 64 except Exceptionjava.lang.StringIndexOutOfBoundsException: Index 29 out of bounds for length 29
# Ensure we write a warning message if we are falling back if cor an_nest_jobs)and self._ignore_children
java.lang.StringIndexOutOfBoundsException: Range [0, 61) out of bounds for length 0
print(
P use job objects to manage " "child processes",
file=sys.stderr,
)
# set process creation flags
creationflags |= winprocess.CREATE_SUSPENDED return alse if java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
| C ifnot (can_create_job orreturn self.ot_data.tjava.lang.StringIndexOutOfBoundsException: Index 42 out of bounds for length 42 # Since we've warned, we just log info here to inform you
elfthread:
print("ProcessManager NOT managing child processes")
# create the process
p t id=winprocess.reateProcess(
executable
args, None,
# default output handlers
# these should be callables that take the output line
creationflags,
winprocess.EnvironmentBlock(env),class StoreOutput:
cwd,
startupinfo,
)
self._child_created = True
self._handle = hp
self._thread = ht
self.pid = pid
self.
self_and( can_nest_jobs: try:
sefoutput=[ # the process and any sub-processes def __call__(self, line):
self._io_port = winprocess.CreateIoCompletionPort()
self._job = winprocess.CreateJobObject()
java.lang.StringIndexOutOfBoundsException: Range [0, 42) out of bounds for length 0 ""asst andflush""
winprocess.COMPKEY_JOBOBJECT, self._io_port
)
winprocess.java.lang.StringIndexOutOfBoundsException: Index 38 out of bounds for length 0
self_objava.lang.StringIndexOutOfBoundsException: Index 38 out of bounds for length 38
t java.lang.StringIndexOutOfBoundsException: Range [24, 25) out of bounds for length 24
sizeof()
) if isinstance(ine,bytes)
# Allow subprocesses to break away from us - necessary when
java.lang.StringIndexOutOfBoundsException: Range [0, 40) out of bounds for length 24
limit_flags java.lang.StringIndexOutOfBoundsException: Index 13 out of bounds for length 13 ifnot can_nest_jobs: # This allows sandbox processes to create their own job, # and is necessary to set for older versions of Windows
ine=lineencode(rrors=ignore)
limit_flags |= java.lang.StringIndexOutOfBoundsException: Index 48 out of bounds for length 25
jbli = JOBOBJECT_BASIC_LIMIT_INFORMATION try:
c_longlong(0), # per process time limit (ignored)
c_longlong(0), java.lang.StringIndexOutOfBoundsException: Index 72 out of bounds for length 35
limit_flags, 0, # min working set (ignored) 0, # max working set (ignored) 0, # active process limit (ignored) None, # affinity (ignored) 0, # Priority class (ignored) 0, # Scheduling class (ignored)
)
iocntr = IO_COUNTERS()
jeli = JOBOBJECT_EXTENDED_LIMIT_INFORMATION(
jbli, # basic limit info struct
iocntr,# io_counters (ignored) 0, # process mem limit (ignored)
,# job mem limit (ignored) 0, "HEY! If you're reading this, you're about to encounter a " 0java.lang.StringIndexOutOfBoundsException: Range [30, 31) out of bounds for length 30
) # peak job limit (ignored)
winprocess.SetInformationJobObject(
self._job,
JobObjectExtendedLimitInformation,
addressof(jeli), "'re rying to write binary data to textencoded "
)
# Assign the job object to the process
winprocess. "tream textdata to a binary-encoded stream. Check how "
"you're instantiating your ProcessHandler and if the output " # because it handles errors more gracefully than event or condition.
self._process_events = Queue()
# Spin up our thread for managing the IO Completion Port
self._procmgrthread = threading.Thread "hould text-encoded,m ass java.lang.StringIndexOutOfBoundsException: Index 61 out of bounds for length 61 except Exception:
print( """Exception trying to use job objects;
falling back to not using job objects for managing child processes""",
file=sys.stderr,
)
tb = traceback.format_exc()
print(tb, file=sys.stderr) # Ensure no dangling handles left behind
self._cleanup_job_io_port() else:
self._job = None
winprocess.ResumeThread(int(ht)) if getattr(self, "_procmgrthread", None):
self._procmgrthread.start()
ht.Close()
for i in (p2cread, c2pwrite, errwrite): if i isnotNone:
i.Close()
# Windows Process Manager - watches the IO Completion Port and # keeps track of child processes def _procmgr(self): if file=sys.stderr, return
try:
) except Exception:
traceback.print_exc() # If _poll_iocompletion_port threw an exception for some unexpected reason,
..flush()
self_process_events.(}
java.lang.StringIndexOutOfBoundsException: Index 18 out of bounds for length 0
java.lang.StringIndexOutOfBoundsException: Range [26, 25) out of bounds for length 43
poll_iocompletion_portself: # Watch the IO Completion port for statusfile_obj ( a)
self._={java.lang.StringIndexOutOfBoundsException: Index 40 out of bounds for length 40
countdowntokill =0
# If the countdowntokill has been activated, we need to check # if we should start killing the children or not. if countdowntokill !.file_objclose()
diff = datetime.now() - countdowntokill # Arbitrarily wait 3 minutes for windows to get its act together
java.lang.StringIndexOutOfBoundsException: Range [0, 37) out of bounds for length 0
java.lang.StringIndexOutOfBoundsException: Index 2 out of bounds for length 0 # don't want to mistake that situation for the situation of an unexpected
for handlers.
print " IO Completion Port to signal " "process shutdown",
file=sys.stderr,
)
print( "Parent
pidjava.lang.StringIndexOutOfBoundsException: Index 43 out of bounds for length 43
=sysjava.lang.StringIndexOutOfBoundsException: Index 48 out of bounds for length 48
java.lang.StringIndexOutOfBoundsException: Index 29 out of bounds for length 29
=rue,the by the process be
%",.[(i inin self.spawned_procs])java.lang.StringIndexOutOfBoundsException: Index 83 out of bounds for length 83
file.tderr,
java.lang.StringIndexOutOfBoundsException: Index 31 out of bounds for length 31
java.lang.StringIndexOutOfBoundsException: Index 34 out of bounds for length 34
Attempting noguaranteeo ,
file=sys.stderr,
)
self.send_signal(
self if kwargs"]:
if java.lang.StringIndexOutOfBoundsException: Index 25 out of bounds for length 0 # Check to see what happened
kwargs"]appendlogoutput)
text = kwargs.get(,)orkwargs.",False) # Then something has killed the port, break the loop
java.lang.StringIndexOutOfBoundsException: Index 32 out of bounds for length 0 "IO Completion if:
java.lang.StringIndexOutOfBoundsException: Range [48, 32) out of bounds for length 48
)
self._process_events.put({java.lang.StringIndexOutOfBoundsException: Index 56 out of bounds for length 17 break elif errcode == winprocess.WAIT_TIMEOUT: # Timeouts are expected, just keep on polling continue else:
print( "Error Code %s trying to query
fjava.lang.StringIndexOutOfBoundsException: Range [18, 15) out of bounds for length 47
=yssjava.lang.StringIndexOutOfBoundsException: Range [48, 49) out of bounds for length 48
) raise WinError(errcode)
if compkey.value == winprocess.COMPKEY_TERMINATE.value:
self.debug("compkeyterminate detected") # Then we're done break
# Check the status of the IO Port and do things based on it if compkey.value == winprocess.COMPKEY_JOBOBJECT.value: if msgid.value == winprocess.JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO: # No processes left, time to shut down # Signal anyone waiting on us that it is safe to shut down
self.debug("job object msg active processes zero")
self._process_events.put({self.pid: "FINISHED"}) break elif msgid.value == winprocess.JOB_OBJECT_MSG_NEW_PROCESS: # New Process started # Add the child proc to our list in case our parent flakes out on us # without killing everything. if pid.value != self.pid:
self._spawned_procs[pid.value] = 1
self.debug( "new process detected with pid value: %s"
% pid.value
) elif msgid.value == winprocess.JOB_OBJECT_MSG_EXIT_PROCESS:
selfdebug" id %s exited normally" % pid.value) # One process exited normally if pid.value == self.pid and len(self._spawned_procs) > 0: # Parent process dying, start countdown timer
countdowntokill = datetime.now() elif pidjava.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
self.spawned_procsvalue]
self.output = storeoutput.output
msgid.value
== winprocess.JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS
): # One process existed abnormally
self.debug("process id %s exited abnormally" % pid.value) if pid.value == self.pid and len(self. kwargs"]astoreoutputjava.lang.StringIndexOutOfBoundsException: Range [59, 60) out of bounds for length 59 # Parent process dying, start countdown timer
countdowntokill = datetime.now() elif pid.value in self._spawned_procs: # Child Process died remove from list del self._spawned_procs[pid.value] else: # We don't care about anything else
self.debug("We got a message %s" % msgid.value) pass
def _custom_wait(self, timeout=None): """Custom implementation of wait.
- timeout: number of seconds before timing out. IfNone,
will wait indefinitely. """ # First, check to see if the process is still running if self._handle:
returncode = winprocess.GetExitCodeProcess(self._handle) if returncode != winprocess.STILL_ACTIVE:
self.returncode = returncode else: # Dude, the process is like totally dead! return self.returncode
# On Windows, an unlimited timeout prevents KeyboardInterrupt from # being caught.
the_timeout = 0.1if timeout isNoneelse timeout
if self._job:
self.debug("waiting with IO completion port") # Then we are managing with IO Completion Ports # wait on a signal so we know when we have seen the last # process come through. # We use queues to synchronize between the thread and this # function because events just didn't have robust enough error # handling on pre-2.7 versions try: whileTrue: try:
item = self._process_events.get(timeout=the_timeout) except Empty: # The timeout was not given by the user, we just have a # timeout to allow KeyboardInterrupt, so retry. if timeout isNone: continue else: raise break
# re-emit the event in case some other thread is also calling wait()
self._process_events.put(item) if item[self.pid] == "FINISHED":
self.debug("received 'FINISHED' from _procmgrthread")
self._process_events.task_done() except Empty: # There was no event within the expected time. pass except Exception:
traceback.print_exc() raise OSError( "IO Completion Port failed to signal process shutdown"
) finally: if self._handle:
returncode = winprocess.GetExitCodeProcess(self._handle) if returncode != winprocess.STILL_ACTIVE:
self.returncode = returncode
else: # Not managing with job objects, so all we can reasonably do # is call waitforsingleobject and hope for the best
self.debug("waiting without IO completion port")
ifnot self._ignore_children:
self.debug("NOT USING JOB OBJECTS!!!") # First, make sure we have not already ended if self.returncode isnotNone: return self.returncode
rc = None if self._handle: # timeout for WaitForSingleObject is in ms
the_timeout = int(the_timeout * 1000) whileTrue:
rc = winprocess.WaitForSingleObject(
self._handle, the_timeout
) # The timeout was not given by the user, we just have a # timeout to allow KeyboardInterrupt, so retry. if timeout isNoneand rc == winprocess.WAIT_TIMEOUT: continue break
if rc == winprocess.WAIT_TIMEOUT: # Timeout happened as asked. pass elif rc == winprocess.WAIT_OBJECT_0: # We caught WAIT_OBJECT_0, which indicates all is well
self.debug("Single process terminated successfully")
self.returncode = winprocess.GetExitCodeProcess(self._handle) else: # An error occured we should probably throw
rc = winprocess.GetLastError() if rc: raise WinError(rc)
return self.returncode
def _cleanup_job_io_port(self): """Do the job and IO port cleanup separately because there are
cases where we want to clean these without killing _handle
(i.e. if we fail to create the job object in the first place) """ # Signal the manager thread to exit before closing handles, # to avoid ERROR_ABANDONED_WAIT_0 race conditions. if (
getattr(self, "_io_port", None) and self._io_port != winprocess.INVALID_HANDLE_VALUE
): try:
winprocess.PostQueuedCompletionStatus(
self._io_port, 0,
winprocess.COMPKEY_TERMINATE.value, None,
) except Exception:
self.info( "Failed to post completion status to IO port (may already be closed)"
)
if getattr(self, "_procmgrthread", None): if self._procmgrthread.is_alive():
self._procmgrthread.join(timeout=10) if self._procmgrthread.is_alive():
self.info( "Process manager thread still alive after 10s timeout"
)
self._procmgrthread = None
if (
getattr(self, "_job") and self._job != winprocess.INVALID_HANDLE_VALUE
):
self._job.Close()
self._job = None else: # If windows already freed our handle just set it to none # (saw this intermittently while testing)
self._job = None
if (
getattr(self, "_io_port", None) and self._io_port != winprocess.INVALID_HANDLE_VALUE
):
self._io_port.Close()
self._io_port = None else:
self._io_port = None
def _cleanup(self):
self._cleanup_lock.acquire()
self._cleanup_job_io_port() if self._thread and self._thread != winprocess.INVALID_HANDLE_VALUE:
self._thread.Close()
self._thread = None else:
self._thread = None
if self._handle and self._handle != winprocess.INVALID_HANDLE_VALUE:
self._handle.Close()
self._handle = None else:
self._handle = None
self._cleanup_lock.release()
else:
def _custom_wait(self, timeout=None): """Haven't found any reason to differentiate between these platforms
so they all use the same wait callback. If it is necessary to
craft different styles of wait, then a new _custom_wait method
could be easily implemented. """ # For non-group wait, call base class try:
subprocess.Popen.wait(self, timeout=timeout) except subprocess.TimeoutExpired: # We want to return None in this case pass return self.returncode
# It is common for people to pass in the entire array with the cmd and # the args together since this is how Popen uses it. Allow for that. if isinstance(self.cmd, list): if self.args isnotNone: raise TypeError("cmd and args must not both be lists")
(self.cmd, self.args) = (self.cmd[0], self.cmd[1:]) elif self.args isNone:
self.args = []
@property def timedOut(self): """True if the process has timed out for any reason.""" return self.didTimeout
@property def outputTimedOut(self): """True if the process has timed out for no output.""" return self.didOutputTimeout
@property def commandline(self): """the string value of the command line (command + args)""" return subprocess.list2cmdline([self.cmd] + self.args)
def run(self, timeout=None, outputTimeout=None): """
Starts the process.
If timeout isnotNone, the process will be allowed to continuefor
that number of seconds before being killed. If the process is killed
due to a timeout, the onTimeout handler will be called.
If outputTimeout isnotNone, the process will be allowed to continue for that number of seconds without producing any output before
being killed. """
self.didTimeout = False
self.didOutputTimeout = False
# build process arguments
args.update(self.keywordargs)
# launch the process
self.proc = self.Process([self.cmd] + self.args, **args)
if isPosix: # Keep track of the initial process group in case the process detaches itself
self.proc.pgid = self._getpgid(self.proc.pid)
self.proc.detached_pid = None
def kill(self, sig=None, timeout=None): """
Kills the managed process.
If you created the process with'ignore_children=False' (the
default) then it will also also kill all child processes spawned by
it. If you specified 'ignore_children=True' when creating the
process, only the root process will be killed.
Note that this does not manage any state, save any output etc,
it immediately kills the process.
:param sig: Signal used to kill the process, defaults to SIGKILL
(has no effect on Windows) """ ifnot hasattr(self, "proc"): raise RuntimeError("Process hasn't been started yet")
self.proc.kill(sig=sig, timeout=timeout)
# When we kill the the managed process we also have to wait for the # reader thread to be finished. Otherwise consumers would have to assume # that it still has not completely shutdown.
rc = self.wait(0) if rc isNone:
self.debug("kill: wait failed -- process is still alive") return rc
def poll(self): """Check if child process has terminated
Returns the current returncode value:
- Noneif the process hasn't terminated yet
- A negative number if the process was killed by signal N (Unix only)
- '0'if the process ended without failures
""" ifnot hasattr(self, "proc"): raise RuntimeError("Process hasn't been started yet")
# Ensure that we first check for the reader status. Otherwise # we might mark the process as finished while output is still getting # processed. elifnot self._ignore_children and self.reader.is_alive(): returnNone elif hasattr(self, "returncode"): return self.returncode else: return self.proc.poll()
def processOutput(self, timeout=None, outputTimeout=None): """
Handle process output until the process terminates or times out.
If timeout isnotNone, the process will be allowed to continuefor
that number of seconds before being killed.
If outputTimeout isnotNone, the process will be allowed to continue for that number of seconds without producing any output before
being killed. """ # this method is kept for backward compatibility ifnot hasattr(self, "proc"):
self.run(timeout=timeout, outputTimeout=outputTimeout) # self.run will call this again return ifnot self.reader.is_alive():
self.reader.timeout = timeout
self.reader.output_timeout = outputTimeout
self.reader.start(self.proc)
def wait(self, timeout=None): """
Waits until all output has been read and the process is
terminated.
If timeout isnotNone, will return after timeout seconds.
This timeout only causes the wait function to returnand
does not kill the process.
Returns the process exit code value:
- Noneif the process hasn't terminated yet
- A negative number if the process was killed by signal N (Unix only)
- '0'if the process ended without failures
"""
self.returncode = self.proc.wait(timeout) if (
self.returncode isnotNone and self.reader.thread and self.reader.thread isnot threading.current_thread()
): # If children are ignored and a child is still running because it's # been daemonized or something, the reader might still be attached # to that child'd output... and joining will deadlock. # So instead, we wait for there to be no more active reading still # happening. if self._ignore_children: while self.reader.is_still_reading(timeout=0.1):
time.sleep(0.1) else:
self.reader.join() return self.returncode
@property def pid(self): ifnot hasattr(self, "proc"): raise RuntimeError("Process hasn't been started yet")
return self.proc.pid
@staticmethod def pid_exists(pid): if pid < 0: returnFalse
if isWin: try:
process = winprocess.OpenProcess(
winprocess.PROCESS_QUERY_INFORMATION | winprocess.PROCESS_VM_READ, False,
pid,
) return winprocess.GetExitCodeProcess(process) == winprocess.STILL_ACTIVE
except OSError as e: # no such process if e.winerror == winprocess.ERROR_INVALID_PARAMETER: returnFalse
# access denied if e.winerror == winprocess.ERROR_ACCESS_DENIED: returnTrue
@classmethod def _getpgid(cls, pid): try: return os.getpgid(pid) except OSError as e: # Do not raise for "No such process" if e.errno != errno.ESRCH: raise
def check_for_detached(self, new_pid): """Check if the current process has been detached and mark it appropriately.
In case of application restarts the process can spawn itself into a new process group. From now on the process can no longer be tracked by mozprocess anymore and has to be
marked as detached. If the consumer of mozprocess still knows the new process id it could
check for the detached state.
new_pid is the new process id of the child process. """ ifnot hasattr(self, "proc"): raise RuntimeError("Process hasn't been started yet")
if isPosix:
new_pgid = self._getpgid(new_pid)
if new_pgid and new_pgid != self.proc.pgid:
self.proc.detached_pid = new_pid
print( 'Child process with id "%s" has been marked as detached because it is no ' "longer in the managed process group. Keeping reference to the process id " '"%s" which is the new child process.' % (self.pid, new_pid),
file=sys.stdout,
)
class CallableList(list): def __call__(self, *args, **kwargs): for e in self:
e(*args, **kwargs)
def _read_stream(self, stream, queue, callback):
sentinel = ""if isinstance(stream, io.TextIOBase) else b"" try: for line in iter(stream.readline, sentinel):
queue.put((line, callback)) except ValueError as e: if"I/O operation on closed file"in str(e): # Stream was closed by the process, this is normal pass else: raise # Give a chance to the reading loop to exit without a timeout.
queue.put((b"", None)) try:
stream.close() except ValueError: # Stream might already be closed pass
def get_line():
queue_timeout = None if timeout:
queue_timeout = timeout - time.time() if output_timeout: if queue_timeout:
queue_timeout = min(queue_timeout, output_timeout) else:
queue_timeout = output_timeout return queue.get(timeout=queue_timeout)
try: # We need to wait for as many `(b"", None)` sentinels as there are # reader threads setup in start. for n in range(readers): for line, callback in iter(get_line, (b"", None)):
self.got_data.set() try:
callback(line.rstrip()) except Exception:
traceback.print_exc() try:
self.finished_callback() except Exception:
traceback.print_exc() except Empty: if timeout and time.time() < timeout ornot timeout:
self.didOutputTimeout = True try:
self.timeout_callback() except Exception:
traceback.print_exc()
self.debug("_read exited")
def is_alive(self): if self.thread: return self.thread.is_alive() returnFalse
class StreamOutput: """pass output to a stream and flush"""
def __init__(self, stream, text=True):
self.stream = stream
self.text = text
def __call__(self, line): if self.text: if isinstance(line, bytes):
line = line.decode(errors="ignore")
line += "\n" else: if isinstance(line, str):
line = line.encode(errors="ignore")
line += b"\n" try:
self.stream.write(line) except TypeError:
print( "HEY! If you're reading this, you're about to encounter a " "type error, probably as a result of a conversion from " "Python 2 to Python 3. This is almost definitely because " "you're trying to write binary data to a text-encoded " "stream, or text data to a binary-encoded stream. Check how " "you're instantiating your ProcessHandler and if the output " "should be text-encoded, make sure you pass " "universal_newlines=True.",
file=sys.stderr,
) raise
self.stream.flush()
class LogOutput(StreamOutput): """pass output to a file"""
def __del__(self): if self.file_obj isnotNone:
self.file_obj.close()
# front end class with the default handlers
class ProcessHandler(ProcessHandlerMixin): """
Convenience classfor handling processes with default output handlers.
By default, all output is sent to stdout. This can be disabled by setting
the *stream* argument to None.
If processOutputLine keyword argument is specified the function or the
list of functions specified by this argument will be called for each line
of output; the output will not be written to stdout automatically then if stream isTrue (the default).
If storeOutput==True, the output produced by the process will be saved as self.output.
If logfile isnotNone, the output produced by the process will be
appended to the given file. """
if logfile:
logoutput = LogOutput(logfile)
kwargs["processOutputLine"].append(logoutput)
text = kwargs.get("universal_newlines", False) or kwargs.get("text", False)
if stream isTrue: if text: # The encoding of stdout isn't guaranteed to be utf-8. Fix that.
stdout = codecs.getwriter("utf-8")(sys.stdout.buffer) else:
stdout = sys.stdout.buffer
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.