timeout.py 1.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
  1. import datetime, subprocess, fcntl, time, os, sys, signal
  2. # exception class
  3. class Timeout(Exception): pass
  4. def command(command, timeout, pollinterval = .1, return_stderr = False):
  5. """call shell-command and either return its output or kill it
  6. if it doesn't normally exit within timeout seconds and return None"""
  7. start = datetime.datetime.now()
  8. process = subprocess.Popen([ 'bash', '-c', command ], stdout = subprocess.PIPE, stderr = subprocess.PIPE)
  9. outtxt = ''; errtxt = ''
  10. outfd = process.stdout.fileno(); fcntl.fcntl(outfd, fcntl.F_SETFL, os.O_NONBLOCK)
  11. errfd = process.stderr.fileno(); fcntl.fcntl(errfd, fcntl.F_SETFL, os.O_NONBLOCK)
  12. while process.poll() is None:
  13. time.sleep(pollinterval)
  14. now = datetime.datetime.now()
  15. if (now - start).seconds > timeout:
  16. try:
  17. os.kill(process.pid, signal.SIGKILL)
  18. os.waitpid(process.pid, os.WNOHANG)
  19. except OSError, e:
  20. pass # ignore [Errno 3] No such process: process may have exited between process.poll() and here
  21. raise Timeout
  22. try: outtxt += os.read(outfd, 4096)
  23. except OSError: pass # ignore EAGAIN (means 'no new data available now' on non-blocking fd)
  24. try: errtxt += os.read(errfd, 4096)
  25. except OSError: pass
  26. while True:
  27. try:
  28. more = os.read(outfd, 4096)
  29. except OSError:
  30. break
  31. if not more: break
  32. outtxt += more
  33. while True:
  34. try:
  35. more = os.read(errfd, 4096)
  36. except OSError:
  37. break
  38. if not more: break
  39. errtxt += more
  40. if return_stderr:
  41. return outtxt, errtxt
  42. else:
  43. if errtxt: sys.stderr.write(errtxt)
  44. return outtxt