# # # patch "mtn.py" # from [7c97c5608d0dad3aa254a53e5bde4ac2fa7a83ac] # to [1f4c058e4f55b848c1c8195a38e6edaeb86310b6] # ============================================================ --- mtn.py 7c97c5608d0dad3aa254a53e5bde4ac2fa7a83ac +++ mtn.py 1f4c058e4f55b848c1c8195a38e6edaeb86310b6 @@ -1,10 +1,19 @@ import os import re +import fcntl import pipes import select import threading +import popen2 +debugging = True + +debug = None + +import web +debug = web.debug + # regular expressions that are of general use when # validating monotone output def group_compile(r): @@ -26,11 +35,29 @@ def abbrev(self): return '[' + self[:8] + '..]' +def set_nonblocking(fd): + fl = fcntl.fcntl(fd, fcntl.F_GETFL) + fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NDELAY) + +def terminate_popen3(process): + debug("%s stopping %s" % (os.getpid(), process.pid)) + try: + process.tochild.close() + process.fromchild.close() + process.childerr.close() + if process.poll() == -1: + # the process is still running, so kill it. + os.kill(process.pid, signal.SIGKILL) + process.wait() + except: + debug("%s failed_to_stop %s" % (os.getpid(), self.process.pid)) + class Runner: def __init__(self, monotone, database): self.base_command = [monotone, "--db=%s" % pipes.quote(database)] packet_header_re = re.compile(r'^(\d+):(\d+):([lm]):(\d+):') +import web class Automate(Runner): """Runs commands via a particular monotone process. This @@ -45,36 +72,43 @@ self.lock = threading.Lock() self.process = None + def stop(self): + if not self.process: + return + terminate_popen3(self.process) + def __process_required(self): if self.process != None: return - from utility import set_nonblocking to_run = self.base_command + ['automate', 'stdio'] - self.child_stdin, self.child_stdout, self.child_stderr = os.popen3(to_run) - map (set_nonblocking, [ self.child_stdin, - self.child_stdout, - self.child_stderr ]) + self.process = popen2.Popen3(to_run, capturestderr=True) + map (set_nonblocking, [ self.process.fromchild, + self.process.tochild, + self.process.childerr ]) def run(self, command, args): if not self.lock.acquire(False): raise MonotoneException("Automate process can't be called: it is already locked.") self.__process_required() + debug(command, args) enc = "l%d:%s" % (len(command), command) enc += ''.join(map(lambda x: "%d:%s" % (len(x), x), args)) + 'e' - self.child_stdin.write(enc) - self.child_stdin.flush() + debug("wrote", enc) + self.process.tochild.write(enc) + self.process.tochild.flush() import sys def read_result_packets(): buffer = "" while True: - r_stdin, r_stdout, r_stderr = select.select([self.child_stdout], [], [], None) + r_stdin, r_stdout, r_stderr = select.select([self.process.fromchild], [], [], None) if not r_stdin and not r_stdout and not r_stderr: break - if self.child_stdout in r_stdin: - data = self.child_stdout.read() + if self.process.fromchild in r_stdin: + data = self.process.fromchild.read() + debug("data", data) if data == "": break buffer += data @@ -88,7 +122,7 @@ break in_packet = True cmdnum, errnum, pstate, length = m.groups() - errnum = int(cmdnum) + errnum = int(errnum) length = int(length) header_length = m.end(m.lastindex) + 1 # the '1' is the colon @@ -113,7 +147,7 @@ for line in data.split('\n'): yield line + '\n' if code_max > 0: - raise MonotoneException("error code %d in automate packet." % code_max) + raise MonotoneException("error code %d in automate packet." % (code_max)) self.lock.release() class Standalone(Runner): @@ -125,13 +159,15 @@ # arguments - and does not pass them through the shell according # to help(os.popen3) to_run = self.base_command + [command] + args - child_stdin, child_stdout, child_stderr = os.popen3(to_run) - for line in child_stdout: + process = popen2.Popen3(to_run, capturestderr=True) + for line in process.fromchild: yield line - stderr_data = child_stderr.read() + stderr_data = process.childerr.read() if len(stderr_data) > 0: raise MonotoneException("data on stderr for command '%s': %s" % (command, stderr_data)) + terminate_popen3(process) + class MtnObject: def __init__(self, obj_type): self.obj_type = obj_type @@ -241,6 +277,9 @@ self.standalone = apply(Standalone, runner_args) self.automate = apply(Automate, runner_args) + def __del__(self): + debug("deleting Operations instance.") + def tags(self): for line in (t.strip() for t in self.standalone.run('ls', ['tags'])): if not line: