|  | #!/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, indent=indent) | 
|  | buffer += gp.sprint_varx("time_out", self.__time_out, indent=indent) | 
|  | buffer += gp.sprint_varx("child_pid", self.__child_pid, indent=indent) | 
|  | buffer += gp.sprint_varx("original_SIGUSR1_handler", | 
|  | self.__original_SIGUSR1_handler, | 
|  | 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, KeyError, TypeError): | 
|  | # NOTE: In python 3, this code fails with "KeyError: | 
|  | # ('__main__',)" when calling functions like lprint_executing that | 
|  | # use inspect.stack() during object destruction.  No fixes found | 
|  | # so tolerating the error.  In python 2.x, it may fail with | 
|  | # TypeError.  This seems to happen when cleaning up after an | 
|  | # exception was raised. | 
|  | 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. | 
|  | try: | 
|  | if self.__time_out.lower() == "none": | 
|  | self.__time_out = None | 
|  | except AttributeError: | 
|  | pass | 
|  | 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 |