Source code for utah.timeout

# Ubuntu Testing Automation Harness
# Copyright 2012 Canonical Ltd.

# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.

# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
# PURPOSE.  See the GNU General Public License for more details.

# You should have received a copy of the GNU General Public License along
# with this program.  If not, see <http://www.gnu.org/licenses/>.

"""Provide functionality to execute command with timeouts."""


import os
import signal
import subprocess
import sys

from utah.commandstr import commandstr


[docs]class UTAHTimeout(SystemExit): """Provide a special exception to indicate an operation timed out.""" pass # Mostly pulled from utah/client/common.py # Inspired by: # http://stackoverflow.com/a/3326559
[docs]def timeout(timeout, command, *args, **kw): """Run a command for up to ``timeout`` seconds. :param timeout: Maximum amount of time to wait for the command to complete in seconds. :type timeout: int :param command: Command whose execution should complete before timeout expires. :type command: callable :param args: Positional arguments to be passed to the callable. :param kwargs: Keyword arguments to be passed to the callable. :returns: The value returned by the callable. :raises UTAHTimeout: If command execution hasn't finished before ``timeout`` seconds. .. seealso:: :func:`utah.retry.retry` """ #TODO: Better support for nested timeouts. if command is None: return def alarm_handler(_signum, _frame): raise UTAHTimeout('{} timed out after {} seconds' .format(commandstr(command, *args, **kw), str(timeout))) if timeout is None: return command(*args, **kw) elif timeout != 0: oldtimeout = signal.alarm(0) if oldtimeout > 0: # We're in a nested timeout. Use the outermost one for now. signal.alarm(oldtimeout) return command(*args, **kw) else: signal.signal(signal.SIGALRM, alarm_handler) signal.alarm(timeout) retval = command(*args, **kw) if timeout != 0: signal.alarm(0) return retval else: raise UTAHTimeout('0 timeout specified')
[docs]def subprocesstimeout(timeout, *args, **kw): """Run command through ``subprocess.Popen`` for up to ``timeout`` seconds. Command termination is checked using ``subprocess.Popen.poll``. :param timeout: Maximum amount of time to wait for the command to complete in seconds. :type timeout: int :param args: Positional arguments to be passed to ``subprocess.Popen``. :param kwargs: Keyword arguments to be passed to the ``subprocess.Popen``. :returns: The subprocess object :rtype: ``subprocess.Popen`` :raises UTAHTimeout: If command execution hasn't finished before ``timeout`` seconds. """ if args is None: return class TimeoutAlarm(Exception): pass def alarm_handler(_signum, _frame): raise TimeoutAlarm if timeout != 0: signal.signal(signal.SIGALRM, alarm_handler) oldtimeout = signal.alarm(timeout) if oldtimeout > 0: signal.alarm(oldtimeout) try: p = subprocess.Popen(*args, **kw) while p.poll() is None: pass if timeout != 0: signal.alarm(0) return p except TimeoutAlarm: pids = [p.pid] # Kill p's children too. pids.extend(get_process_children(p.pid)) for pid in pids: # process might have died before getting to this line # so wrap to avoid OSError: no such process try: os.kill(pid, signal.SIGKILL) except OSError: pass msg = ('{} timed out after {} seconds' .format(' '.join(args[0]), str(timeout))) raise UTAHTimeout(msg)
[docs]def get_process_children(pid, logmethod=sys.stderr.write): """Find process children so they can be killed when the timeout expires. :param pid: Process ID for the parent process :type pid: int :returns: The pid for each children process :rtype: list(int) """ try: pids = subprocess.check_output(['ps', '--no-headers', '-o', 'pid', '--ppid', pid]).split() return [int(p) for p in pids] except subprocess.CalledProcessError: return [] except OSError as err: logmethod('Could not kill process children: {}'.format(err)) return []
Read the Docs v: latest
Versions
latest
Downloads
PDF
HTML
Epub
On Read the Docs
Project Home
Builds

Free document hosting provided by Read the Docs.