| #!/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, KeyError): | 
 |             # 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. | 
 |             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 |