2012-11-20 06:28:12 -07:00
|
|
|
#!/usr/bin/env python
|
2013-04-05 10:44:48 -06:00
|
|
|
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
|
|
|
|
|
|
|
import os, sys
|
|
|
|
import signal
|
|
|
|
import threading
|
|
|
|
import subprocess
|
|
|
|
from subprocess import Popen, PIPE, STDOUT
|
|
|
|
|
|
|
|
# **heavily** chopped up and modfied version of asyncproc.py
|
|
|
|
# to make it actually work on Windows as well as Mac/Linux
|
|
|
|
# For the original see:
|
|
|
|
# "http://www.lysator.liu.se/~bellman/download/"
|
|
|
|
# author is "Thomas Bellman <bellman@lysator.liu.se>"
|
|
|
|
# available under GPL version 3 or Later
|
|
|
|
|
|
|
|
# create an asynchronous subprocess whose output can be collected in
|
|
|
|
# a non-blocking manner
|
|
|
|
|
|
|
|
# What a mess! Have to use threads just to get non-blocking io
|
|
|
|
# in a cross-platform manner
|
|
|
|
|
|
|
|
# luckily all thread use is hidden within this class
|
|
|
|
|
|
|
|
class Process(object):
|
|
|
|
def __init__(self, *params, **kwparams):
|
|
|
|
if len(params) <= 3:
|
|
|
|
kwparams.setdefault('stdin', subprocess.PIPE)
|
|
|
|
if len(params) <= 4:
|
|
|
|
kwparams.setdefault('stdout', subprocess.PIPE)
|
|
|
|
if len(params) <= 5:
|
|
|
|
kwparams.setdefault('stderr', subprocess.PIPE)
|
|
|
|
self.__pending_input = []
|
|
|
|
self.__collected_outdata = []
|
|
|
|
self.__collected_errdata = []
|
|
|
|
self.__exitstatus = None
|
|
|
|
self.__lock = threading.Lock()
|
|
|
|
self.__inputsem = threading.Semaphore(0)
|
|
|
|
self.__quit = False
|
|
|
|
|
|
|
|
self.__process = subprocess.Popen(*params, **kwparams)
|
|
|
|
|
|
|
|
if self.__process.stdin:
|
|
|
|
self.__stdin_thread = threading.Thread(
|
|
|
|
name="stdin-thread",
|
|
|
|
target=self.__feeder, args=(self.__pending_input,
|
|
|
|
self.__process.stdin))
|
|
|
|
self.__stdin_thread.setDaemon(True)
|
|
|
|
self.__stdin_thread.start()
|
|
|
|
|
|
|
|
if self.__process.stdout:
|
|
|
|
self.__stdout_thread = threading.Thread(
|
|
|
|
name="stdout-thread",
|
|
|
|
target=self.__reader, args=(self.__collected_outdata,
|
|
|
|
self.__process.stdout))
|
|
|
|
self.__stdout_thread.setDaemon(True)
|
|
|
|
self.__stdout_thread.start()
|
|
|
|
|
|
|
|
if self.__process.stderr:
|
|
|
|
self.__stderr_thread = threading.Thread(
|
|
|
|
name="stderr-thread",
|
|
|
|
target=self.__reader, args=(self.__collected_errdata,
|
|
|
|
self.__process.stderr))
|
|
|
|
self.__stderr_thread.setDaemon(True)
|
|
|
|
self.__stderr_thread.start()
|
|
|
|
|
|
|
|
def pid(self):
|
|
|
|
return self.__process.pid
|
|
|
|
|
|
|
|
def kill(self, signal):
|
|
|
|
self.__process.send_signal(signal)
|
|
|
|
|
|
|
|
# check on subprocess (pass in 'nowait') to act like poll
|
|
|
|
def wait(self, flag):
|
|
|
|
if flag.lower() == 'nowait':
|
|
|
|
rc = self.__process.poll()
|
2012-11-20 06:28:12 -07:00
|
|
|
else:
|
2013-04-05 10:44:48 -06:00
|
|
|
rc = self.__process.wait()
|
|
|
|
if rc != None:
|
|
|
|
if self.__process.stdin:
|
|
|
|
self.closeinput()
|
|
|
|
if self.__process.stdout:
|
|
|
|
self.__stdout_thread.join()
|
|
|
|
if self.__process.stderr:
|
|
|
|
self.__stderr_thread.join()
|
|
|
|
return self.__process.returncode
|
|
|
|
|
|
|
|
def terminate(self):
|
|
|
|
if self.__process.stdin:
|
|
|
|
self.closeinput()
|
|
|
|
self.__process.terminate()
|
|
|
|
|
|
|
|
# thread gets data from subprocess stdout
|
|
|
|
def __reader(self, collector, source):
|
|
|
|
while True:
|
|
|
|
data = os.read(source.fileno(), 65536)
|
|
|
|
self.__lock.acquire()
|
|
|
|
collector.append(data)
|
|
|
|
self.__lock.release()
|
|
|
|
if data == "":
|
|
|
|
source.close()
|
2012-11-20 06:28:12 -07:00
|
|
|
break
|
2013-04-05 10:44:48 -06:00
|
|
|
return
|
|
|
|
|
|
|
|
# thread feeds data to subprocess stdin
|
|
|
|
def __feeder(self, pending, drain):
|
|
|
|
while True:
|
|
|
|
self.__inputsem.acquire()
|
|
|
|
self.__lock.acquire()
|
|
|
|
if not pending and self.__quit:
|
|
|
|
drain.close()
|
|
|
|
self.__lock.release()
|
|
|
|
break
|
|
|
|
data = pending.pop(0)
|
|
|
|
self.__lock.release()
|
|
|
|
drain.write(data)
|
|
|
|
|
|
|
|
# non-blocking read of data from subprocess stdout
|
|
|
|
def read(self):
|
|
|
|
self.__lock.acquire()
|
|
|
|
outdata = "".join(self.__collected_outdata)
|
|
|
|
del self.__collected_outdata[:]
|
|
|
|
self.__lock.release()
|
|
|
|
return outdata
|
|
|
|
|
|
|
|
# non-blocking read of data from subprocess stderr
|
|
|
|
def readerr(self):
|
|
|
|
self.__lock.acquire()
|
|
|
|
errdata = "".join(self.__collected_errdata)
|
|
|
|
del self.__collected_errdata[:]
|
|
|
|
self.__lock.release()
|
|
|
|
return errdata
|
|
|
|
|
|
|
|
# non-blocking write to stdin of subprocess
|
|
|
|
def write(self, data):
|
|
|
|
if self.__process.stdin is None:
|
|
|
|
raise ValueError("Writing to process with stdin not a pipe")
|
|
|
|
self.__lock.acquire()
|
|
|
|
self.__pending_input.append(data)
|
|
|
|
self.__inputsem.release()
|
|
|
|
self.__lock.release()
|
|
|
|
|
|
|
|
# close stdinput of subprocess
|
|
|
|
def closeinput(self):
|
|
|
|
self.__lock.acquire()
|
|
|
|
self.__quit = True
|
|
|
|
self.__inputsem.release()
|
|
|
|
self.__lock.release()
|