blob: e4855e7cf5e7cdfe77ce82bb58ea2fc00c0cad99 [file] [log] [blame]
#!/usr/bin/env python
r"""
Define the func_timer class.
"""
import os
import sys
import signal
import time
import gen_print as gp
import gen_misc as gm
import gen_valid as gv
class func_timer_class:
r"""
Define the func timer class.
A func timer object can be used to run any function/arguments but with an
additional benefit of being able to specify a time_out value. If the
function fails to complete before the timer expires, a ValueError
exception will be raised along with a detailed error message.
Example code:
func_timer = func_timer_class()
func_timer.run(run_key, "sleep 2", time_out=1)
In this example, the run_key function is being run by the func_timer
object with a time_out value of 1 second. "sleep 2" is a positional parm
for the run_key function.
"""
def __init__(self,
obj_name='func_timer_class'):
# Initialize object variables.
self.__obj_name = obj_name
self.__func = None
self.__time_out = None
self.__child_pid = 0
# Save the original SIGUSR1 handler for later restoration by this
# class' methods.
self.__original_SIGUSR1_handler = signal.getsignal(signal.SIGUSR1)
def __del__(self):
self.cleanup()
def sprint_obj(self):
r"""
sprint the fields of this object. This would normally be for debug
purposes.
"""
buffer = ""
buffer += self.__class__.__name__ + ":\n"
indent = 2
try:
func_name = self.__func.__name__
except AttributeError:
func_name = ""
buffer += gp.sprint_var(func_name, hex=1, loc_col1_indent=indent)
buffer += gp.sprint_varx("time_out", self.__time_out,
loc_col1_indent=indent)
buffer += gp.sprint_varx("child_pid", self.__child_pid,
loc_col1_indent=indent)
buffer += gp.sprint_varx("original_SIGUSR1_handler",
self.__original_SIGUSR1_handler,
loc_col1_indent=indent)
return buffer
def print_obj(self):
r"""
print the fields of this object to stdout. This would normally be for
debug purposes.
"""
sys.stdout.write(self.sprint_obj())
def cleanup(self):
r"""
Cleanup after the run method.
"""
try:
gp.lprint_executing()
gp.lprint_var(self.__child_pid)
except AttributeError:
pass
# If self.__child_pid is 0, then we are either running as the child
# or we've already cleaned up.
# If self.__time_out is None, then no child process would have been
# spawned.
if self.__child_pid == 0 or self.__time_out is None:
return
# Restore the original SIGUSR1 handler.
if self.__original_SIGUSR1_handler != 0:
signal.signal(signal.SIGUSR1, self.__original_SIGUSR1_handler)
try:
gp.lprint_timen("Killing child pid " + str(self.__child_pid)
+ ".")
os.kill(self.__child_pid, signal.SIGKILL)
except OSError:
gp.lprint_timen("Tolerated kill failure.")
try:
gp.lprint_timen("os.waitpid(" + str(self.__child_pid) + ")")
os.waitpid(self.__child_pid, 0)
except OSError:
gp.lprint_timen("Tolerated waitpid failure.")
self.__child_pid = 0
# For debug purposes, prove that the child pid was killed.
children = gm.get_child_pids()
gp.lprint_var(children)
def timed_out(self,
signal_number,
frame):
r"""
Handle a SIGUSR1 generated by the child process after the time_out has
expired.
signal_number The signal_number of the signal causing
this method to get invoked. This should
always be 10 (SIGUSR1).
frame The stack frame associated with the
function that times out.
"""
gp.lprint_executing()
self.cleanup()
# Compose an error message.
err_msg = "The " + self.__func.__name__
err_msg += " function timed out after " + str(self.__time_out)
err_msg += " seconds.\n"
if not gp.robot_env:
err_msg += gp.sprint_call_stack()
raise ValueError(err_msg)
def run(self, func, *args, **kwargs):
r"""
Run the indicated function with the given args and kwargs and return
the value that the function returns. If the time_out value expires,
raise a ValueError exception with a detailed error message.
This method passes all of the args and kwargs directly to the child
function with the following important exception: If kwargs contains a
'time_out' value, it will be used to set the func timer object's
time_out value and then the kwargs['time_out'] entry will be removed.
If the time-out expires before the function finishes running, this
method will raise a ValueError.
Example:
func_timer = func_timer_class()
func_timer.run(run_key, "sleep 3", time_out=2)
Example:
try:
result = func_timer.run(func1, "parm1", time_out=2)
print_var(result)
except ValueError:
print("The func timed out but we're handling it.")
Description of argument(s):
func The function object which is to be called.
args The arguments which are to be passed to
the function object.
kwargs The keyword arguments which are to be
passed to the function object. As noted
above, kwargs['time_out'] will get special
treatment.
"""
gp.lprint_executing()
# Store method parms as object parms.
self.__func = func
# Get self.__time_out value from kwargs. If kwargs['time_out'] is
# not present, self.__time_out will default to None.
self.__time_out = None
if 'time_out' in kwargs:
self.__time_out = kwargs['time_out']
del kwargs['time_out']
# Convert "none" string to None.
if type(self.__time_out) in (str, unicode)\
and self.__time_out.lower() == "none":
self.__time_out = None
if self.__time_out is not None:
self.__time_out = int(self.__time_out)
# Ensure that time_out is non-negative.
message = gv.svalid_range(self.__time_out, [0], "time_out")
if message != "":
raise ValueError("\n"
+ gp.sprint_error_report(message,
format='long'))
gp.lprint_varx("time_out", self.__time_out)
self.__child_pid = 0
if self.__time_out is not None:
# Save the original SIGUSR1 handler for later restoration by this
# class' methods.
self.__original_SIGUSR1_handler = signal.getsignal(signal.SIGUSR1)
# Designate a SIGUSR1 handling function.
signal.signal(signal.SIGUSR1, self.timed_out)
parent_pid = os.getpid()
self.__child_pid = os.fork()
if self.__child_pid == 0:
gp.dprint_timen("Child timer pid " + str(os.getpid())
+ ": Sleeping for " + str(self.__time_out)
+ " seconds.")
time.sleep(self.__time_out)
gp.dprint_timen("Child timer pid " + str(os.getpid())
+ ": Sending SIGUSR1 to parent pid "
+ str(parent_pid) + ".")
os.kill(parent_pid, signal.SIGUSR1)
os._exit(0)
# Call the user's function with the user's arguments.
children = gm.get_child_pids()
gp.lprint_var(children)
gp.lprint_timen("Calling the user's function.")
gp.lprint_varx("func_name", func.__name__)
gp.lprint_vars(args, kwargs)
try:
result = func(*args, **kwargs)
except Exception as func_exception:
# We must handle all exceptions so that we have the chance to
# cleanup before re-raising the exception.
gp.lprint_timen("Encountered exception in user's function.")
self.cleanup()
raise(func_exception)
gp.lprint_timen("Returned from the user's function.")
self.cleanup()
return result