aboutsummaryrefslogtreecommitdiffstats
path: root/lib/python2.7/site-packages/buildbot_slave-0.8.8-py2.7.egg/buildslave/runprocess.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/python2.7/site-packages/buildbot_slave-0.8.8-py2.7.egg/buildslave/runprocess.py')
-rw-r--r--lib/python2.7/site-packages/buildbot_slave-0.8.8-py2.7.egg/buildslave/runprocess.py859
1 files changed, 0 insertions, 859 deletions
diff --git a/lib/python2.7/site-packages/buildbot_slave-0.8.8-py2.7.egg/buildslave/runprocess.py b/lib/python2.7/site-packages/buildbot_slave-0.8.8-py2.7.egg/buildslave/runprocess.py
deleted file mode 100644
index 7dd8dc7b..00000000
--- a/lib/python2.7/site-packages/buildbot_slave-0.8.8-py2.7.egg/buildslave/runprocess.py
+++ /dev/null
@@ -1,859 +0,0 @@
-# This file is part of Buildbot. Buildbot is free software: you can
-# redistribute it and/or modify it under the terms of the GNU General Public
-# License as published by the Free Software Foundation, version 2.
-#
-# This program is distributed in the hope that it will be useful, but WITHOUT
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
-# details.
-#
-# You should have received a copy of the GNU General Public License along with
-# this program; if not, write to the Free Software Foundation, Inc., 51
-# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-#
-# Copyright Buildbot Team Members
-
-"""
-Support for running 'shell commands'
-"""
-
-import sys
-import os
-import signal
-import types
-import re
-import subprocess
-import traceback
-import stat
-from collections import deque
-from tempfile import NamedTemporaryFile
-
-from twisted.python import runtime, log
-from twisted.python.win32 import quoteArguments
-from twisted.internet import reactor, defer, protocol, task, error
-
-from buildslave import util
-from buildslave.exceptions import AbandonChain
-
-if runtime.platformType == 'posix':
- from twisted.internet.process import Process
-
-def shell_quote(cmd_list):
- # attempt to quote cmd_list such that a shell will properly re-interpret
- # it. The pipes module is only available on UNIX, and Windows "shell"
- # quoting is indescribably convoluted - so much so that it's not clear it's
- # reversible. Also, the quote function is undocumented (although it looks
- # like it will be documentd soon: http://bugs.python.org/issue9723).
- # Finally, it has a nasty bug in some versions where an empty string is not
- # quoted.
- #
- # So:
- # - use pipes.quote on UNIX, handling '' as a special case
- # - use Python's repr() on Windows, as a best effort
- if runtime.platformType == 'win32':
- return " ".join([ `e` for e in cmd_list ])
- else:
- import pipes
- def quote(e):
- if not e:
- return '""'
- return pipes.quote(e)
- return " ".join([ quote(e) for e in cmd_list ])
-
-class LogFileWatcher:
- POLL_INTERVAL = 2
-
- def __init__(self, command, name, logfile, follow=False):
- self.command = command
- self.name = name
- self.logfile = logfile
-
- log.msg("LogFileWatcher created to watch %s" % logfile)
- # we are created before the ShellCommand starts. If the logfile we're
- # supposed to be watching already exists, record its size and
- # ctime/mtime so we can tell when it starts to change.
- self.old_logfile_stats = self.statFile()
- self.started = False
-
- # follow the file, only sending back lines
- # added since we started watching
- self.follow = follow
-
- # every 2 seconds we check on the file again
- self.poller = task.LoopingCall(self.poll)
-
- def start(self):
- self.poller.start(self.POLL_INTERVAL).addErrback(self._cleanupPoll)
-
- def _cleanupPoll(self, err):
- log.err(err, msg="Polling error")
- self.poller = None
-
- def stop(self):
- self.poll()
- if self.poller is not None:
- self.poller.stop()
- if self.started:
- self.f.close()
-
- def statFile(self):
- if os.path.exists(self.logfile):
- s = os.stat(self.logfile)
- return (s[stat.ST_CTIME], s[stat.ST_MTIME], s[stat.ST_SIZE])
- return None
-
- def poll(self):
- if not self.started:
- s = self.statFile()
- if s == self.old_logfile_stats:
- return # not started yet
- if not s:
- # the file was there, but now it's deleted. Forget about the
- # initial state, clearly the process has deleted the logfile
- # in preparation for creating a new one.
- self.old_logfile_stats = None
- return # no file to work with
- self.f = open(self.logfile, "rb")
- # if we only want new lines, seek to
- # where we stat'd so we only find new
- # lines
- if self.follow:
- self.f.seek(s[2], 0)
- self.started = True
- self.f.seek(self.f.tell(), 0)
- while True:
- data = self.f.read(10000)
- if not data:
- return
- self.command.addLogfile(self.name, data)
-
-
-if runtime.platformType == 'posix':
- class ProcGroupProcess(Process):
- """Simple subclass of Process to also make the spawned process a process
- group leader, so we can kill all members of the process group."""
-
- def _setupChild(self, *args, **kwargs):
- Process._setupChild(self, *args, **kwargs)
-
- # this will cause the child to be the leader of its own process group;
- # it's also spelled setpgrp() on BSD, but this spelling seems to work
- # everywhere
- os.setpgid(0, 0)
-
-
-class RunProcessPP(protocol.ProcessProtocol):
- debug = False
-
- def __init__(self, command):
- self.command = command
- self.pending_stdin = ""
- self.stdin_finished = False
- self.killed = False
-
- def setStdin(self, data):
- assert not self.connected
- self.pending_stdin = data
-
- def connectionMade(self):
- if self.debug:
- log.msg("RunProcessPP.connectionMade")
-
- if self.command.useProcGroup:
- if self.debug:
- log.msg(" recording pid %d as subprocess pgid"
- % (self.transport.pid,))
- self.transport.pgid = self.transport.pid
-
- if self.pending_stdin:
- if self.debug: log.msg(" writing to stdin")
- self.transport.write(self.pending_stdin)
- if self.debug: log.msg(" closing stdin")
- self.transport.closeStdin()
-
- def outReceived(self, data):
- if self.debug:
- log.msg("RunProcessPP.outReceived")
- self.command.addStdout(data)
-
- def errReceived(self, data):
- if self.debug:
- log.msg("RunProcessPP.errReceived")
- self.command.addStderr(data)
-
- def processEnded(self, status_object):
- if self.debug:
- log.msg("RunProcessPP.processEnded", status_object)
- # status_object is a Failure wrapped around an
- # error.ProcessTerminated or and error.ProcessDone.
- # requires twisted >= 1.0.4 to overcome a bug in process.py
- sig = status_object.value.signal
- rc = status_object.value.exitCode
-
- # sometimes, even when we kill a process, GetExitCodeProcess will still return
- # a zero exit status. So we force it. See
- # http://stackoverflow.com/questions/2061735/42-passed-to-terminateprocess-sometimes-getexitcodeprocess-returns-0
- if self.killed and rc == 0:
- log.msg("process was killed, but exited with status 0; faking a failure")
- # windows returns '1' even for signalled failures, while POSIX returns -1
- if runtime.platformType == 'win32':
- rc = 1
- else:
- rc = -1
- self.command.finished(sig, rc)
-
-
-class RunProcess:
- """
- This is a helper class, used by slave commands to run programs in a child
- shell.
- """
-
- notreally = False
- BACKUP_TIMEOUT = 5
- interruptSignal = "KILL"
- CHUNK_LIMIT = 128*1024
-
- # Don't send any data until at least BUFFER_SIZE bytes have been collected
- # or BUFFER_TIMEOUT elapsed
- BUFFER_SIZE = 64*1024
- BUFFER_TIMEOUT = 5
-
- # For sending elapsed time:
- startTime = None
- elapsedTime = None
-
- # For scheduling future events
- _reactor = reactor
-
- # I wish we had easy access to CLOCK_MONOTONIC in Python:
- # http://www.opengroup.org/onlinepubs/000095399/functions/clock_getres.html
- # Then changes to the system clock during a run wouldn't effect the "elapsed
- # time" results.
-
- def __init__(self, builder, command,
- workdir, environ=None,
- sendStdout=True, sendStderr=True, sendRC=True,
- timeout=None, maxTime=None, initialStdin=None,
- keepStdout=False, keepStderr=False,
- logEnviron=True, logfiles={}, usePTY="slave-config",
- useProcGroup=True):
- """
-
- @param keepStdout: if True, we keep a copy of all the stdout text
- that we've seen. This copy is available in
- self.stdout, which can be read after the command
- has finished.
- @param keepStderr: same, for stderr
-
- @param usePTY: "slave-config" -> use the SlaveBuilder's usePTY;
- otherwise, true to use a PTY, false to not use a PTY.
-
- @param useProcGroup: (default True) use a process group for non-PTY
- process invocations
- """
-
- self.builder = builder
-
- # We need to take unicode commands and arguments and encode them using
- # the appropriate encoding for the slave. This is mostly platform
- # specific, but can be overridden in the slave's buildbot.tac file.
- #
- # Encoding the command line here ensures that the called executables
- # receive arguments as bytestrings encoded with an appropriate
- # platform-specific encoding. It also plays nicely with twisted's
- # spawnProcess which checks that arguments are regular strings or
- # unicode strings that can be encoded as ascii (which generates a
- # warning).
- def to_str(cmd):
- if isinstance(cmd, (tuple, list)):
- for i, a in enumerate(cmd):
- if isinstance(a, unicode):
- cmd[i] = a.encode(self.builder.unicode_encoding)
- elif isinstance(cmd, unicode):
- cmd = cmd.encode(self.builder.unicode_encoding)
- return cmd
-
- self.command = to_str(util.Obfuscated.get_real(command))
- self.fake_command = to_str(util.Obfuscated.get_fake(command))
-
- self.sendStdout = sendStdout
- self.sendStderr = sendStderr
- self.sendRC = sendRC
- self.logfiles = logfiles
- self.workdir = workdir
- self.process = None
- if not os.path.exists(workdir):
- os.makedirs(workdir)
- if environ:
- for key, v in environ.iteritems():
- if isinstance(v, list):
- # Need to do os.pathsep translation. We could either do that
- # by replacing all incoming ':'s with os.pathsep, or by
- # accepting lists. I like lists better.
- # If it's not a string, treat it as a sequence to be
- # turned in to a string.
- environ[key] = os.pathsep.join(environ[key])
-
- if environ.has_key('PYTHONPATH'):
- environ['PYTHONPATH'] += os.pathsep + "${PYTHONPATH}"
-
- # do substitution on variable values matching pattern: ${name}
- p = re.compile('\${([0-9a-zA-Z_]*)}')
- def subst(match):
- return os.environ.get(match.group(1), "")
- newenv = {}
- for key in os.environ.keys():
- # setting a key to None will delete it from the slave environment
- if key not in environ or environ[key] is not None:
- newenv[key] = os.environ[key]
- for key, v in environ.iteritems():
- if v is not None:
- if not isinstance(v, basestring):
- raise RuntimeError("'env' values must be strings or "
- "lists; key '%s' is incorrect" % (key,))
- newenv[key] = p.sub(subst, v)
-
- self.environ = newenv
- else: # not environ
- self.environ = os.environ.copy()
- self.initialStdin = initialStdin
- self.logEnviron = logEnviron
- self.timeout = timeout
- self.timer = None
- self.maxTime = maxTime
- self.maxTimer = None
- self.keepStdout = keepStdout
- self.keepStderr = keepStderr
-
- self.buffered = deque()
- self.buflen = 0
- self.buftimer = None
-
- if usePTY == "slave-config":
- self.usePTY = self.builder.usePTY
- else:
- self.usePTY = usePTY
-
- # usePTY=True is a convenience for cleaning up all children and
- # grandchildren of a hung command. Fall back to usePTY=False on systems
- # and in situations where ptys cause problems. PTYs are posix-only,
- # and for .closeStdin to matter, we must use a pipe, not a PTY
- if runtime.platformType != "posix" or initialStdin is not None:
- if self.usePTY and usePTY != "slave-config":
- self.sendStatus({'header': "WARNING: disabling usePTY for this command"})
- self.usePTY = False
-
- # use an explicit process group on POSIX, noting that usePTY always implies
- # a process group.
- if runtime.platformType != 'posix':
- useProcGroup = False
- elif self.usePTY:
- useProcGroup = True
- self.useProcGroup = useProcGroup
-
- self.logFileWatchers = []
- for name,filevalue in self.logfiles.items():
- filename = filevalue
- follow = False
-
- # check for a dictionary of options
- # filename is required, others are optional
- if type(filevalue) == dict:
- filename = filevalue['filename']
- follow = filevalue.get('follow', False)
-
- w = LogFileWatcher(self, name,
- os.path.join(self.workdir, filename),
- follow=follow)
- self.logFileWatchers.append(w)
-
- def __repr__(self):
- return "<%s '%s'>" % (self.__class__.__name__, self.fake_command)
-
- def sendStatus(self, status):
- self.builder.sendUpdate(status)
-
- def start(self):
- # return a Deferred which fires (with the exit code) when the command
- # completes
- if self.keepStdout:
- self.stdout = ""
- if self.keepStderr:
- self.stderr = ""
- self.deferred = defer.Deferred()
- try:
- self._startCommand()
- except:
- log.msg("error in RunProcess._startCommand")
- log.err()
- self._addToBuffers('stderr', "error in RunProcess._startCommand\n")
- self._addToBuffers('stderr', traceback.format_exc())
- self._sendBuffers()
- # pretend it was a shell error
- self.deferred.errback(AbandonChain(-1))
- return self.deferred
-
- def _startCommand(self):
- # ensure workdir exists
- if not os.path.isdir(self.workdir):
- os.makedirs(self.workdir)
- log.msg("RunProcess._startCommand")
- if self.notreally:
- self._addToBuffers('header', "command '%s' in dir %s" % \
- (self.fake_command, self.workdir))
- self._addToBuffers('header', "(not really)\n")
- self.finished(None, 0)
- return
-
- self.pp = RunProcessPP(self)
-
- self.using_comspec = False
- if type(self.command) in types.StringTypes:
- if runtime.platformType == 'win32':
- argv = os.environ['COMSPEC'].split() # allow %COMSPEC% to have args
- if '/c' not in argv: argv += ['/c']
- argv += [self.command]
- self.using_comspec = True
- else:
- # for posix, use /bin/sh. for other non-posix, well, doesn't
- # hurt to try
- argv = ['/bin/sh', '-c', self.command]
- display = self.fake_command
- else:
- # On windows, CreateProcess requires an absolute path to the executable.
- # When we call spawnProcess below, we pass argv[0] as the executable.
- # So, for .exe's that we have absolute paths to, we can call directly
- # Otherwise, we should run under COMSPEC (usually cmd.exe) to
- # handle path searching, etc.
- if runtime.platformType == 'win32' and not \
- (self.command[0].lower().endswith(".exe") and os.path.isabs(self.command[0])):
- argv = os.environ['COMSPEC'].split() # allow %COMSPEC% to have args
- if '/c' not in argv: argv += ['/c']
- argv += list(self.command)
- self.using_comspec = True
- else:
- argv = self.command
- # Attempt to format this for use by a shell, although the process isn't perfect
- display = shell_quote(self.fake_command)
-
- # $PWD usually indicates the current directory; spawnProcess may not
- # update this value, though, so we set it explicitly here. This causes
- # weird problems (bug #456) on msys, though..
- if not self.environ.get('MACHTYPE', None) == 'i686-pc-msys':
- self.environ['PWD'] = os.path.abspath(self.workdir)
-
- # self.stdin is handled in RunProcessPP.connectionMade
-
- log.msg(" " + display)
- self._addToBuffers('header', display+"\n")
-
- # then comes the secondary information
- msg = " in dir %s" % (self.workdir,)
- if self.timeout:
- if self.timeout == 1:
- unit = "sec"
- else:
- unit = "secs"
- msg += " (timeout %d %s)" % (self.timeout, unit)
- if self.maxTime:
- if self.maxTime == 1:
- unit = "sec"
- else:
- unit = "secs"
- msg += " (maxTime %d %s)" % (self.maxTime, unit)
- log.msg(" " + msg)
- self._addToBuffers('header', msg+"\n")
-
- msg = " watching logfiles %s" % (self.logfiles,)
- log.msg(" " + msg)
- self._addToBuffers('header', msg+"\n")
-
- # then the obfuscated command array for resolving unambiguity
- msg = " argv: %s" % (self.fake_command,)
- log.msg(" " + msg)
- self._addToBuffers('header', msg+"\n")
-
- # then the environment, since it sometimes causes problems
- if self.logEnviron:
- msg = " environment:\n"
- env_names = self.environ.keys()
- env_names.sort()
- for name in env_names:
- msg += " %s=%s\n" % (name, self.environ[name])
- log.msg(" environment: %s" % (self.environ,))
- self._addToBuffers('header', msg)
-
- if self.initialStdin:
- msg = " writing %d bytes to stdin" % len(self.initialStdin)
- log.msg(" " + msg)
- self._addToBuffers('header', msg+"\n")
-
- msg = " using PTY: %s" % bool(self.usePTY)
- log.msg(" " + msg)
- self._addToBuffers('header', msg+"\n")
-
- # put data into stdin and close it, if necessary. This will be
- # buffered until connectionMade is called
- if self.initialStdin:
- self.pp.setStdin(self.initialStdin)
-
- self.startTime = util.now(self._reactor)
-
- # start the process
-
- self.process = self._spawnProcess(
- self.pp, argv[0], argv,
- self.environ,
- self.workdir,
- usePTY=self.usePTY)
-
- # set up timeouts
-
- if self.timeout:
- self.timer = self._reactor.callLater(self.timeout, self.doTimeout)
-
- if self.maxTime:
- self.maxTimer = self._reactor.callLater(self.maxTime, self.doMaxTimeout)
-
- for w in self.logFileWatchers:
- w.start()
-
- def _spawnProcess(self, processProtocol, executable, args=(), env={},
- path=None, uid=None, gid=None, usePTY=False, childFDs=None):
- """private implementation of reactor.spawnProcess, to allow use of
- L{ProcGroupProcess}"""
-
- # use the ProcGroupProcess class, if available
- if runtime.platformType == 'posix':
- if self.useProcGroup and not usePTY:
- return ProcGroupProcess(reactor, executable, args, env, path,
- processProtocol, uid, gid, childFDs)
-
- # fall back
- if self.using_comspec:
- return self._spawnAsBatch(processProtocol, executable, args, env,
- path, usePTY=usePTY)
- else:
- return reactor.spawnProcess(processProtocol, executable, args, env,
- path, usePTY=usePTY)
-
- def _spawnAsBatch(self, processProtocol, executable, args, env,
- path, usePTY):
- """A cheat that routes around the impedance mismatch between
- twisted and cmd.exe with respect to escaping quotes"""
-
- tf = NamedTemporaryFile(dir='.',suffix=".bat",delete=False)
- #echo off hides this cheat from the log files.
- tf.write( "@echo off\n" )
- if type(self.command) in types.StringTypes:
- tf.write( self.command )
- else:
- def maybe_escape_pipes(arg):
- if arg != '|':
- return arg.replace('|','^|')
- else:
- return '|'
- cmd = [maybe_escape_pipes(arg) for arg in self.command]
- tf.write( quoteArguments(cmd) )
- tf.close()
-
- argv = os.environ['COMSPEC'].split() # allow %COMSPEC% to have args
- if '/c' not in argv: argv += ['/c']
- argv += [tf.name]
-
- def unlink_temp(result):
- os.unlink(tf.name)
- return result
- self.deferred.addBoth(unlink_temp)
-
- return reactor.spawnProcess(processProtocol, executable, argv, env,
- path, usePTY=usePTY)
-
- def _chunkForSend(self, data):
- """
- limit the chunks that we send over PB to 128k, since it has a hardwired
- string-size limit of 640k.
- """
- LIMIT = self.CHUNK_LIMIT
- for i in range(0, len(data), LIMIT):
- yield data[i:i+LIMIT]
-
- def _collapseMsg(self, msg):
- """
- Take msg, which is a dictionary of lists of output chunks, and
- concatentate all the chunks into a single string
- """
- retval = {}
- for log in msg:
- data = "".join(msg[log])
- if isinstance(log, tuple) and log[0] == 'log':
- retval['log'] = (log[1], data)
- else:
- retval[log] = data
- return retval
-
- def _sendMessage(self, msg):
- """
- Collapse and send msg to the master
- """
- if not msg:
- return
- msg = self._collapseMsg(msg)
- self.sendStatus(msg)
-
- def _bufferTimeout(self):
- self.buftimer = None
- self._sendBuffers()
-
- def _sendBuffers(self):
- """
- Send all the content in our buffers.
- """
- msg = {}
- msg_size = 0
- lastlog = None
- logdata = []
- while self.buffered:
- # Grab the next bits from the buffer
- logname, data = self.buffered.popleft()
-
- # If this log is different than the last one, then we have to send
- # out the message so far. This is because the message is
- # transferred as a dictionary, which makes the ordering of keys
- # unspecified, and makes it impossible to interleave data from
- # different logs. A future enhancement could be to change the
- # master to support a list of (logname, data) tuples instead of a
- # dictionary.
- # On our first pass through this loop lastlog is None
- if lastlog is None:
- lastlog = logname
- elif logname != lastlog:
- self._sendMessage(msg)
- msg = {}
- msg_size = 0
- lastlog = logname
-
- logdata = msg.setdefault(logname, [])
-
- # Chunkify the log data to make sure we're not sending more than
- # CHUNK_LIMIT at a time
- for chunk in self._chunkForSend(data):
- if len(chunk) == 0: continue
- logdata.append(chunk)
- msg_size += len(chunk)
- if msg_size >= self.CHUNK_LIMIT:
- # We've gone beyond the chunk limit, so send out our
- # message. At worst this results in a message slightly
- # larger than (2*CHUNK_LIMIT)-1
- self._sendMessage(msg)
- msg = {}
- logdata = msg.setdefault(logname, [])
- msg_size = 0
- self.buflen = 0
- if logdata:
- self._sendMessage(msg)
- if self.buftimer:
- if self.buftimer.active():
- self.buftimer.cancel()
- self.buftimer = None
-
- def _addToBuffers(self, logname, data):
- """
- Add data to the buffer for logname
- Start a timer to send the buffers if BUFFER_TIMEOUT elapses.
- If adding data causes the buffer size to grow beyond BUFFER_SIZE, then
- the buffers will be sent.
- """
- n = len(data)
-
- self.buflen += n
- self.buffered.append((logname, data))
- if self.buflen > self.BUFFER_SIZE:
- self._sendBuffers()
- elif not self.buftimer:
- self.buftimer = self._reactor.callLater(self.BUFFER_TIMEOUT, self._bufferTimeout)
-
- def addStdout(self, data):
- if self.sendStdout:
- self._addToBuffers('stdout', data)
-
- if self.keepStdout:
- self.stdout += data
- if self.timer:
- self.timer.reset(self.timeout)
-
- def addStderr(self, data):
- if self.sendStderr:
- self._addToBuffers('stderr', data)
-
- if self.keepStderr:
- self.stderr += data
- if self.timer:
- self.timer.reset(self.timeout)
-
- def addLogfile(self, name, data):
- self._addToBuffers( ('log', name), data)
-
- if self.timer:
- self.timer.reset(self.timeout)
-
- def finished(self, sig, rc):
- self.elapsedTime = util.now(self._reactor) - self.startTime
- log.msg("command finished with signal %s, exit code %s, elapsedTime: %0.6f" % (sig,rc,self.elapsedTime))
- for w in self.logFileWatchers:
- # this will send the final updates
- w.stop()
- self._sendBuffers()
- if sig is not None:
- rc = -1
- if self.sendRC:
- if sig is not None:
- self.sendStatus(
- {'header': "process killed by signal %d\n" % sig})
- self.sendStatus({'rc': rc})
- self.sendStatus({'header': "elapsedTime=%0.6f\n" % self.elapsedTime})
- if self.timer:
- self.timer.cancel()
- self.timer = None
- if self.maxTimer:
- self.maxTimer.cancel()
- self.maxTimer = None
- if self.buftimer:
- self.buftimer.cancel()
- self.buftimer = None
- d = self.deferred
- self.deferred = None
- if d:
- d.callback(rc)
- else:
- log.msg("Hey, command %s finished twice" % self)
-
- def failed(self, why):
- self._sendBuffers()
- log.msg("RunProcess.failed: command failed: %s" % (why,))
- if self.timer:
- self.timer.cancel()
- self.timer = None
- if self.maxTimer:
- self.maxTimer.cancel()
- self.maxTimer = None
- if self.buftimer:
- self.buftimer.cancel()
- self.buftimer = None
- d = self.deferred
- self.deferred = None
- if d:
- d.errback(why)
- else:
- log.msg("Hey, command %s finished twice" % self)
-
- def doTimeout(self):
- self.timer = None
- msg = "command timed out: %d seconds without output" % self.timeout
- self.kill(msg)
-
- def doMaxTimeout(self):
- self.maxTimer = None
- msg = "command timed out: %d seconds elapsed" % self.maxTime
- self.kill(msg)
-
- def kill(self, msg):
- # This may be called by the timeout, or when the user has decided to
- # abort this build.
- self._sendBuffers()
- if self.timer:
- self.timer.cancel()
- self.timer = None
- if self.maxTimer:
- self.maxTimer.cancel()
- self.maxTimer = None
- if self.buftimer:
- self.buftimer.cancel()
- self.buftimer = None
- msg += ", attempting to kill"
- log.msg(msg)
- self.sendStatus({'header': "\n" + msg + "\n"})
-
- # let the PP know that we are killing it, so that it can ensure that
- # the exit status comes out right
- self.pp.killed = True
-
- # keep track of whether we believe we've successfully killed something
- hit = 0
-
- # try signalling the process group
- if not hit and self.useProcGroup and runtime.platformType == "posix":
- sig = getattr(signal, "SIG"+ self.interruptSignal, None)
-
- if sig is None:
- log.msg("signal module is missing SIG%s" % self.interruptSignal)
- elif not hasattr(os, "kill"):
- log.msg("os module is missing the 'kill' function")
- elif self.process.pgid is None:
- log.msg("self.process has no pgid")
- else:
- log.msg("trying to kill process group %d" %
- (self.process.pgid,))
- try:
- os.kill(-self.process.pgid, sig)
- log.msg(" signal %s sent successfully" % sig)
- self.process.pgid = None
- hit = 1
- except OSError:
- log.msg('failed to kill process group (ignored): %s' %
- (sys.exc_info()[1],))
- # probably no-such-process, maybe because there is no process
- # group
- pass
-
- elif runtime.platformType == "win32":
- if self.interruptSignal == None:
- log.msg("self.interruptSignal==None, only pretending to kill child")
- elif self.process.pid is not None:
- log.msg("using TASKKILL /F PID /T to kill pid %s" % self.process.pid)
- subprocess.check_call("TASKKILL /F /PID %s /T" % self.process.pid)
- log.msg("taskkill'd pid %s" % self.process.pid)
- hit = 1
-
- # try signalling the process itself (works on Windows too, sorta)
- if not hit:
- try:
- log.msg("trying process.signalProcess('%s')" % (self.interruptSignal,))
- self.process.signalProcess(self.interruptSignal)
- log.msg(" signal %s sent successfully" % (self.interruptSignal,))
- hit = 1
- except OSError:
- log.err("from process.signalProcess:")
- # could be no-such-process, because they finished very recently
- pass
- except error.ProcessExitedAlready:
- log.msg("Process exited already - can't kill")
- # the process has already exited, and likely finished() has
- # been called already or will be called shortly
- pass
-
- if not hit:
- log.msg("signalProcess/os.kill failed both times")
-
- if runtime.platformType == "posix":
- # we only do this under posix because the win32eventreactor
- # blocks here until the process has terminated, while closing
- # stderr. This is weird.
- self.pp.transport.loseConnection()
-
- if self.deferred:
- # finished ought to be called momentarily. Just in case it doesn't,
- # set a timer which will abandon the command.
- self.timer = self._reactor.callLater(self.BACKUP_TIMEOUT,
- self.doBackupTimeout)
-
- def doBackupTimeout(self):
- log.msg("we tried to kill the process, and it wouldn't die.."
- " finish anyway")
- self.timer = None
- self.sendStatus({'header': "SIGKILL failed to kill process\n"})
- if self.sendRC:
- self.sendStatus({'header': "using fake rc=-1\n"})
- self.sendStatus({'rc': -1})
- self.failed(RuntimeError("SIGKILL failed to kill process"))