HELP - os.waitpid() in a thread not working right under linux?
Chester Gingrich
gingrich at his.com
Sat Jan 25 11:52:47 EST 2003
I am trying to "wrap" a process in a thread so that i can control an
external program. Specifically, I want to limit the time that an
external program will run - but not limit it to ending sooner...
I found a script that seems to provide this functionality under
windows and supposedly under unix. However, on my redhat linux
(kernel 2.4.18-19.8.0, python version 2.2.1) system the
os.waitpid(pid,0) call throws an exception indicating that the child
has died - BUT - it hasnt! I have attached the "newrun.py" script and
a script (sleeper.py) that i use for testing. If anyone knows
something about this issue, please let me know.
In an old newsgroups posting i have seen that linux python had trouble
running os.waitpid() from within a thread - any ideas whats going on
here?
tia
-Chester Gingrich
gingrich at his.com
************
newrun.py
************
#!/usr/bin/env python
#-----------------------------------------------------------------------------
# process.py: Simple process management
#
# This module defines the following classes:
#
# Process - Abstract base class for a process.
# Win32Process - a WIN32 process
# PosixProcess - a POSIX (Unix) process
#
# Requires:
# Python 1.5.2 or >;
# Win32 extensions on Windows
# Threads enabled on POSIX
# OS: Win32, Posix (Unix)
#
# History:
# -28 Nov 2000: created by Richard Gruet
#
# TODO
#-----------------------------------------------------------------------------
''' Simple process management.
Allows to launch, kill and see the status of a process independently
of the platform. Use global function createProcess(cmd) to create a
process for executing a command.
'''
__version__ = '1.0.1'
__author__ = 'Richard Gruet', 'rgruet at ina.fr'
__date__ = '2000-11-28'
import os, sys, string, time
true, false = -1, 0
class Error(Exception):
''' Exception raised by the process module.
'''
pass
#-----------------------------------------------------------------------------
class Process:
#-----------------------------------------------------------------------------
''' Abstract base class for a Process.
Derive specialized classes for each OS.
'''
MIN_LIFE_TIME = 1.0 # Minimum lifetime of a process in sec
def __init__(self, cmd, pid):
''' Runs the program <cmd> in a new process.
NB: this base class constructor MUST be called
by derived classes to initialize attributes.
@param cmd The program command line.
@exception Error if program cannot be launched.
'''
self.cmd = cmd
self.pid = pid
self.exitCode = None
self.startedOn = time.time()
def __repr__(self):
exitCode = self.getExitCode()
if exitCode is None:
state = 'alive'
else:
state = 'ended, exit code=%d' % exitCode
return '<%s pid=%d, %s>' % (self.__class__.__name__,self.pid,
state)
def getCmd(self):
''' Returns the command string executed by the process.
'''
return self.cmd
def getPid(self):
''' Returns the PID of the process.
'''
return self.pid
def wait(self, timeout=None):
''' Waits until process terminates or a timeout occurs.
@param timeout Time out in seconds (None=infinite).
@return the exit code, or None if timeout.
'''
raise NotImplementedError()
def kill(self, hard=true):
''' Kills the process.
Silent if the process is already terminated.
The process will have an exit code = -1
@param hard [Unix] if true use signal SIGKILL,
otherwise SIGTERM.
'''
raise NotImplementedError()
def getExitCode(self):
''' Returns the exit code of the process, or None if still
alive.
On Posix the process must be a child process of the
caller; in some
circumstances (kill) the exit status cannot be
determined and will
arbitrarily be returned as -1.
The returned exit code is also assigned to
self.exitCode.
'''
raise NotImplementedError()
def isAlive(self):
''' Returns true if process is alive.
'''
return self.getExitCode() is None
# Private:
def _ensureMinLifeTime(self, minTime=None):
''' Sleeps until the process has lived at least <minTime> sec.
This is used to avoid killing the process too fast
(this leads
to strange result codes).
'''
if minTime is None:
minTime = self.MIN_LIFE_TIME
delta = time.time() - (self.startedOn + minTime)
if delta < 0:
time.sleep(-delta)
if sys.platform == 'win32':
try:
from win32process import CreateProcess,
TerminateProcess,GetExitCodeProcess, STARTUPINFO
import win32event
except ImportError, e:
raise Error("Can't import win32process, have you installed the
"
"Python win32 extensions ?")
#-----------------------------------------------------------------------------
class Win32Process(Process):
#-----------------------------------------------------------------------------
''' A Win32 process.
'''
def __init__(self, cmd):
''' See class Process.
'''
hp, ht, pid, tid = CreateProcess(None, cmd, None,None, 1,
0,None, None, STARTUPINFO())
self.handle = hp # [PyHANDLE] Win32 process handle
Process.__init__(self, cmd, pid)
def wait(self, timeout=None):
''' See class Process.
'''
if timeout is not None:
timeout = int(timeout * 1000) # in ms
else:
timeout = win32event.INFINITE
win32event.WaitForSingleObject(self.handle, timeout)
return self.getExitCode()
def kill(self, hard=true):
''' See class Process.
'''
if self.isAlive():
print "killed a process under windows"
self._ensureMinLifeTime()
TerminateProcess(self.handle, -1)
self.getExitCode() # refresh attributes
def getExitCode(self):
''' See class Process.
'''
exitCode = GetExitCodeProcess(self.handle)
if exitCode <> 259: # (259 means alive, keep
exitCode==None)
self.exitCode = exitCode
return self.exitCode
else: # not on Win32
class Win32Process: pass
if os.name == 'posix':
import threading
#-----------------------------------------------------------------------------
class PosixProcess(Process):
#-----------------------------------------------------------------------------
''' A POSIX (Unix) process.
'''
def __init__(self, cmd):
''' See class Process.
'''
l = string.split(cmd)
progName, args = l[0], l[1:]
pid = os.fork()
print "process pid is : " + str(pid)
if not pid: # in new process
print "progName=" +str(progName)
print "args=" + str(args)
os.execvp(progName, args)
else:
Process.__init__(self, cmd, pid)
# To be able to catch the exit status we must 'wait'
the end
# of the child process in a separate thread:
self.waitThread =
threading.Thread(target=self._threadMain)
self.waitThread.start()
def wait(self, timeout=None):
''' See class Process.
'''
if self.isAlive():
print "in process wait(): process is alive"
else:
print "in process wait(): process is dead!"
self.waitThread.join(timeout)
btest = self.isAlive()
if btest:
print "in process wait()- after waitThread: process is
alive"
else:
print "in process wait()- after waitThread: process is
dead!"
return self.exitCode
def kill(self, hard=true):
''' See class Process.
'''
if self.isAlive():
print "killed a process under unix"
import signal
if hard:
sig = 9 # SIGKILL
else:
sig = signal.SIGTERM
self._ensureMinLifeTime()
os.kill(self.pid, sig)
self.waitThread.join()
def getExitCode(self):
''' See class Process.
'''
return self.exitCode
def _threadMain(self):
''' Waits thread main proc.
Just waits for the end of the child process,
stores the exit status and exits.
'''
try:
pid, self.exitCode = os.waitpid(self.pid, os.WNOHANG)
except OSError, e:
print "Exception: e.errno = " + str(e.errno)
if e.errno == 10: # child process probably killed
self.exitCode = -1 # not very meaningful !!
else: # not on POSIX
class PosixProcess: pass
#-----------------------------------------------------------------------------
def createProcess(cmd):
#-----------------------------------------------------------------------------
''' Runs the program <cmd> in a new process.
@param cmd The program command line.
@return a Process instance
@exception Error if program cannot be launched.
'''
if sys.platform == 'win32':
p = Win32Process(cmd)
elif os.name == 'posix':
p = PosixProcess(cmd)
else:
raise Error('Only Win32 and Posix (Unix) are supported.')
return p
#-----------------------------------------------------------------------------
# T E S T S
#-----------------------------------------------------------------------------
def test():
print 'Testing module "process"'
if sys.platform == 'win32':
cmd = 'c:\python22\python.exe
C:\cygwin\home\Administrator\sleeper.py'
else:
cmd = '/home/cgg/sleeper.py tst'
p = createProcess(cmd)
if p.isAlive():
print "process is alive"
else:
print "process is dead!"
p.wait(10) # will wait at most this long - works under windows
p.kill()
return p
#-----------------------------------------------------------------------------
# M A I N
#-----------------------------------------------------------------------------
if __name__ == "__main__":
p = test()
***********
sleeper.py
***********
#!/usr/bin/env python
import time
i=0
while 1:
i=i+1
time.sleep(1)
print i
if i>20: break
*************
(sorry for the large posting)
-Chester Gingrich
gingrich at his.com
More information about the Python-list
mailing list