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