#! /usr/bin/env python import sys, os def pidIsAlive(pid, sleepInterval=-0.5): """Calls os.getpgid() to see if a process exists. If sleepInterval is positive then it will pause sleepInterval seconds and try again. os.getpgid will raise an OSError with errno != errno.ESRCH if there is no such process (indicating the process is not dead) """ import errno try: os.getpgid(pid) if sleepInterval > 0.0: import time time.sleep(sleepInterval) #hackity-hack os.getpgid(pid) except OSError, x: if x.errno != errno.ESRCH: raise x return False return True def killPID(pid, verbose=False, sleepInterval=0.5): """uses os.kill with SIGTERM and then SIGKILL and then queries the process ID to verify that it is dead. Returns True if the process is gone (killed or nonexistent) Raises OSError on errors. Returns False if the signals are sent, but the process is not killed.""" import errno import signal if verbose: print sys.argv[0] + ": killing process " + str(pid) try: os.kill(pid, signal.SIGTERM) except OSError, x: if x.errno == errno.ESRCH: if verbose: print "%s: No such process (%d)" % (sys.argv[0], pid) return True else: raise x # if sigterm worked, then we won't get any group id when we query if not pidIsAlive(pid, sleepInterval): return True # the pid is still running if verbose: print sys.argv[0] + ": Kill with SIGTERM Failed. Trying kill of process " + str(pid) + " with SIGKILL" os.kill(pid, signal.SIGKILL) if pidIsAlive(sleepInterval): return False if verbose: print sys.argv[0] + ": killed." return True def killWindowsPID(pid, dir, verbose=False): import subprocess # assumes that the caller guarantees the validity of dir outFilepath = os.path.join(dir, "stdout") errFilepath = os.path.join(dir, "stderr") outObj = open(outFilepath, "w") errObj = open(errFilepath, "w") killer = subprocess.Popen("TASKKILL /F /T /PID %d" % pid, stdout=outObj, stderr=errObj) result = killer.wait() if result != 0: if result == 128: if verbose: print "%s: No such process (%d)" % (sys.argv[0], pid) return True else: raise EnvironmentError, "TASKKILL failed" if verbose: print sys.argv[0] + ": killed." return True def killBackgroundProcess(dir, verbose=False): """Reads the PID file written by launchBackgroundProcess and kills the processes referred to in the file.""" if not os.path.exists(dir): raise ValueError, "The directory %s does not exist" % dir pidFilePath = os.path.join(dir, "pid") if not os.path.exists(pidFilePath): raise ValueError, "The file with a PID (%s) does not exist" % os.path.abspath(pidFilePath) pidFileObj = open(pidFilePath, "rU") if verbose: print sys.argv[0] + ": Reading pids from " + pidFilePath for n, line in enumerate(pidFileObj): try: pid = int(line.strip()) except: raise ValueError, "Expecting lines constisting of just process IDs. Found\n%s at line %d of %s" % (line, n + 1, pidFilePath) if sys.platform == 'win32': killWindowsPID(pid, dir, verbose) else: killPID(pid, verbose) pidFileObj.close() if verbose: print sys.argv[0], ": Removing " + pidFilePath os.remove(pidFilePath) if verbose: print sys.argv[0], ": Finished" def launchBackgroundProcess(exe, dir, verbose=False): if not os.path.exists(exe): raise ValueError, "The executable %s does not exist" % exe if not os.path.exists(dir): os.makedirs(dir) if verbose: print "%s: %s created." % (sys.argv[0], os.path.abspath(dir)) pidFilePath = os.path.join(dir, "pid") if os.path.exists(pidFilePath): raise ValueError, "The file with a PID (%s) already exists. Call " % os.path.abspath(pidFilePath) import subprocess outFilepath = os.path.join(dir, "stdout") errFilepath = os.path.join(dir, "stderr") outObj = open(outFilepath, "w") errObj = open(errFilepath, "w") if verbose: print "Launching:\n%s >%s 2>%s &" %(exe, outFilepath, errFilepath) pid = subprocess.Popen([exe], stdout=outObj, stderr=errObj).pid if verbose: print "PID = %d" % pid pidFileObj = open(pidFilePath, "w") pidFileObj.write("%d\n" % pid) pidFileObj.close() if __name__ == '__main__': from optparse import OptionParser parser = OptionParser() parser.add_option("-k", "--kill", action="store_true", dest="kill", default=False, help="kill the background process") parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False, help="controls the amount of standard output") parser.add_option("-d", "--dir", dest="dir", help="directory for files associated with this launch", metavar="FILE") parser.add_option("-x", "--executable", dest="exe", help="The executable to launch", metavar="FILE") (options, args) = parser.parse_args() try: if options.kill: if options.dir is None: sys.exit('dir must be specified when killing a background process') killBackgroundProcess(dir=options.dir, verbose=options.verbose) else: if options.dir is None: sys.exit('dir must be specified for the redirection of standard output') if options.exe is None: sys.exit('The executable to launch must be specified for the redirection of standard output') launchBackgroundProcess(exe=options.exe, dir=options.dir, verbose=options.verbose) except Exception, x: import traceback traceback.print_exc(file=sys.stdout) sys.exit(sys.argv[0] + " ERROR:\n " + str(x))