set timeout for a shell command in python

I wanted to run a shell command in python without knowing if the shell command is going to exit within reasonable time (adplay that was, sometimes it simply hangs).

Update: the "task" module of Rob Hooft seems to solve this exact problem. At the time I wrote this, the python.net website was down. I leave my solution here just for archive purpose.

PYTHON:
  1. def timeout_command(command, timeout):
  2.     """call shell-command and either return its output or kill it
  3.     if it doesn't normally exit within timeout seconds and return None"""
  4.     import subprocess, datetime, os, time, signal
  5.     start = datetime.datetime.now()
  6.     process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  7.     while process.poll() is None:
  8.         time.sleep(0.1)
  9.         now = datetime.datetime.now()
  10.         if (now - start).seconds> timeout:
  11.             os.kill(process.pid, signal.SIGKILL)
  12.             os.waitpid(-1, os.WNOHANG)
  13.             return None
  14.     return process.stdout.read()

PYTHON:
  1. >>> output = timeout_command(["sleep", "10"], 2)
  2. None
  3. >>> output = timeout_command(["sleep", "1"], 2)

The process can be killed when it has run for too long (the os.waitpid waits for the kill to end and avoids defunct-processes) and furthermore the Popen'ed process' printed is caught and returned if it doesn't timeout. However, subprocess.Popen is called with a list as argument. That means, that the command isn't passed to a shell and furthermore you can just call one command with options, nothing more.

7 Comments

  1. Juan Manuel Caicedo Says:

    I tried to use the function but I found two problems.

    First, the command argument is passed as a string, not a list. Using a string I was having the following error:

    OSError: [Errno 2] No such file or directory

    Then, a 'No such process' error appeared when the process has run for too long. That happend when the os.kill function was called for the second time. I added a 'return None' to fix this problem.

  2. philipp.keller Says:

    Thanks Juan, I fixed the function and the example so the errors won't occur anymore. Sorry for the inconvenience.

  3. Joe Rantala Says:

    Note that os.kil is not available on windows. Too bad :-(

  4. Lee Steensland Says:

    With a one more line of code, and a change to another, your function will act exactly like popen: Note, this requires python 2.4+ Enjoy!

    PYTHON:
    1. def TIMEOUT_COMMAND(command, timeout):
    2.     """call shell-command and either return its output or kill it
    3.     if it doesn't normally exit within timeout seconds and return None"""
    4.     import subprocess, datetime, os, time, signal
    5.     cmd = command.split(" ")
    6.     start = datetime.datetime.now()
    7.     process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    8.     while process.poll() is None:
    9.         time.sleep(0.2)
    10.         now = datetime.datetime.now()
    11.         if (now - start).seconds> timeout:
    12.             os.kill(process.pid, signal.SIGKILL)
    13.             os.waitpid(-1, os.WNOHANG)
    14.             return None
    15.     return process.stdout.readlines()

  5. Giovanni Bajo Says:

    For Windows, you can use:

    import ctypes
    TerminateProcess = ctypes.windll.kernel32.TerminateProcess
    TerminateProcess(int(process.handle), -1)

  6. wrybread Says:

    Small typo in the above Windows code. Should read:

    import ctypes
    TerminateProcess = ctypes.windll.kernel32.TerminateProcess
    TerminateProcess(int(popen._handle), -1)

    (Note the underscore added before "handle").

  7. wrybread Says:

    Also note that the process module has timeout built in. No need to poll, no need to terminate the spawned process:

    http://trentm.com/projects/process/

    import process
    try:
    p = process.ProcessOpen("notepad.exe")
    p.wait(timeout=5)
    except process.ProcessError:
    print "timeout!"

Leave a Reply