| #!/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 |