Thursday, March 5, 2009

Timing out an arbitrary Python function

Update:

The problem with the signal.alarm approach described below is that the alarm WILL happen, thus interrupting a hung function in the failure mode we're trying to account for. But it will ALSO SIGNAL if that function returned as expected, thus throwing an exception at some random point in later code.

The solution is to disable the alarm, as described below:


Occasionally, child processes die. If you don't want your script to hang, you do something like this:

TIMEOUT = 10
process = subprocess.Popen(...)
(o, _, _) = select.select( [process.stdout], [], [], TIMEOUT )
if o:
return process.stdout.readline()
else:
os.kill(process.pid, signal.SIGKILL)
os.waitpid(-1, os.WNOHANG)

This works perfectly well (and mimics the C version pretty closely), but what if the process is already wrapped in a python module and you don't have direct access to the process?

This guy has a good solution:
  def raiseTimeout(a,b):
raise Exception()
signal.signal(signal.SIGALRM, raiseTimeout) # bind a signal handler
signal.alarm(1) # raise the alarm sig in 1 s
some_hang_prone_function() # this is the problem function
signal.alarm(0) # disable the previously set alarm


My original version used a lambda in place of a proper function, but it turns out that lambda a,b: raise Exception() isn't proper syntax.

No comments: