1#!/usr/bin/env python 2 3r""" 4Define the func_timer class. 5""" 6 7import os 8import sys 9import signal 10import time 11import gen_print as gp 12import gen_misc as gm 13 14 15class func_timer_class: 16 r""" 17 Define the func timer class. 18 19 A func timer object can be used to run any function/arguments but with an 20 additional benefit of being able to specify a time_out value. If the 21 function fails to complete before the timer expires, a ValueError 22 exception will be raised along with a detailed error message. 23 24 Example code: 25 26 func_timer = func_timer_class() 27 func_timer.run(run_key, "sleep 2", time_out=1) 28 29 In this example, the run_key function is being run by the func_timer 30 object with a time_out value of 1 second. "sleep 2" is a positional parm 31 for the run_key function. 32 """ 33 34 def __init__(self, 35 obj_name='func_timer_class'): 36 37 # Initialize object variables. 38 self.__obj_name = obj_name 39 self.__func = None 40 self.__time_out = None 41 self.__child_pid = 0 42 # Save the original SIGUSR1 handler for later restoration by this 43 # class' methods. 44 self.__original_SIGUSR1_handler = signal.getsignal(signal.SIGUSR1) 45 46 def __del__(self): 47 self.cleanup() 48 49 def sprint_obj(self): 50 r""" 51 sprint the fields of this object. This would normally be for debug 52 purposes. 53 """ 54 55 buffer = "" 56 buffer += self.__class__.__name__ + ":\n" 57 indent = 2 58 try: 59 func_name = self.__func.__name__ 60 except AttributeError: 61 func_name = "" 62 buffer += gp.sprint_var(func_name, hex=1, loc_col1_indent=indent) 63 buffer += gp.sprint_varx("time_out", self.__time_out, 64 loc_col1_indent=indent) 65 buffer += gp.sprint_varx("child_pid", self.__child_pid, 66 loc_col1_indent=indent) 67 buffer += gp.sprint_varx("original_SIGUSR1_handler", 68 self.__original_SIGUSR1_handler, 69 loc_col1_indent=indent) 70 return buffer 71 72 def print_obj(self): 73 r""" 74 print the fields of this object to stdout. This would normally be for 75 debug purposes. 76 """ 77 78 sys.stdout.write(self.sprint_obj()) 79 80 def cleanup(self): 81 r""" 82 Cleanup after the run method. 83 """ 84 85 try: 86 gp.lprint_executing() 87 gp.lprint_var(self.__child_pid) 88 except AttributeError: 89 pass 90 91 # If self.__child_pid is 0, then we are either running as the child 92 # or we've already cleaned up. 93 # If self.__time_out is None, then no child process would have been 94 # spawned. 95 if self.__child_pid == 0 or self.__time_out is None: 96 return 97 98 # Restore the original SIGUSR1 handler. 99 if self.__original_SIGUSR1_handler != 0: 100 signal.signal(signal.SIGUSR1, self.__original_SIGUSR1_handler) 101 try: 102 gp.lprint_timen("Killing child pid " + str(self.__child_pid) 103 + ".") 104 os.kill(self.__child_pid, signal.SIGKILL) 105 except OSError: 106 gp.lprint_timen("Tolerated kill failure.") 107 try: 108 gp.lprint_timen("os.waitpid(" + str(self.__child_pid) + ")") 109 os.waitpid(self.__child_pid, 0) 110 except OSError: 111 gp.lprint_timen("Tolerated waitpid failure.") 112 self.__child_pid = 0 113 # For debug purposes, prove that the child pid was killed. 114 children = gm.get_child_pids() 115 gp.lprint_var(children) 116 117 def timed_out(self, 118 signal_number, 119 frame): 120 r""" 121 Handle a SIGUSR1 generated by the child process after the time_out has 122 expired. 123 124 signal_number The signal_number of the signal causing 125 this method to get invoked. This should 126 always be 10 (SIGUSR1). 127 frame The stack frame associated with the 128 function that times out. 129 """ 130 131 gp.lprint_executing() 132 133 self.cleanup() 134 135 # Compose an error message. 136 err_msg = "The " + self.__func.__name__ 137 err_msg += " function timed out after " + str(self.__time_out) 138 err_msg += " seconds.\n" 139 if not gp.robot_env: 140 err_msg += gp.sprint_call_stack() 141 142 raise ValueError(err_msg) 143 144 def run(self, func, *args, **kwargs): 145 146 r""" 147 Run the indicated function with the given args and kwargs and return 148 the value that the function returns. If the time_out value expires, 149 raise a ValueError exception with a detailed error message. 150 151 This method passes all of the args and kwargs directly to the child 152 function with the following important exception: If kwargs contains a 153 'time_out' value, it will be used to set the func timer object's 154 time_out value and then the kwargs['time_out'] entry will be removed. 155 If the time-out expires before the function finishes running, this 156 method will raise a ValueError. 157 158 Example: 159 func_timer = func_timer_class() 160 func_timer.run(run_key, "sleep 3", time_out=2) 161 162 Example: 163 try: 164 result = func_timer.run(func1, "parm1", time_out=2) 165 print_var(result) 166 except ValueError: 167 print("The func timed out but we're handling it.") 168 169 Description of argument(s): 170 func The function object which is to be called. 171 args The arguments which are to be passed to 172 the function object. 173 kwargs The keyword arguments which are to be 174 passed to the function object. As noted 175 above, kwargs['time_out'] will get special 176 treatment. 177 """ 178 179 gp.lprint_executing() 180 181 # Store method parms as object parms. 182 self.__func = func 183 184 # Get self.__time_out value from kwargs. If kwargs['time_out'] is 185 # not present, self.__time_out will default to None. 186 self.__time_out = None 187 if len(kwargs) > 0: 188 if 'time_out' in kwargs: 189 self.__time_out = kwargs['time_out'] 190 del kwargs['time_out'] 191 if self.__time_out is not None: 192 self.__time_out = int(self.__time_out) 193 194 self.__child_pid = 0 195 if self.__time_out is not None: 196 # Save the original SIGUSR1 handler for later restoration by this 197 # class' methods. 198 self.__original_SIGUSR1_handler = signal.getsignal(signal.SIGUSR1) 199 # Designate a SIGUSR1 handling function. 200 signal.signal(signal.SIGUSR1, self.timed_out) 201 parent_pid = os.getpid() 202 self.__child_pid = os.fork() 203 if self.__child_pid == 0: 204 gp.dprint_timen("Child timer pid " + str(os.getpid()) 205 + ": Sleeping for " + str(self.__time_out) 206 + " seconds.") 207 time.sleep(self.__time_out) 208 gp.dprint_timen("Child timer pid " + str(os.getpid()) 209 + ": Sending SIGUSR1 to parent pid " 210 + str(parent_pid) + ".") 211 os.kill(parent_pid, signal.SIGUSR1) 212 os._exit(0) 213 214 # Call the user's function with the user's arguments. 215 children = gm.get_child_pids() 216 gp.lprint_var(children) 217 gp.lprint_timen("Calling the user's function.") 218 gp.lprint_varx("func_name", func.__name__) 219 gp.lprint_vars(args, kwargs) 220 try: 221 result = func(*args, **kwargs) 222 except Exception as func_exception: 223 # We must handle all exceptions so that we have the chance to 224 # cleanup before re-raising the exception. 225 gp.lprint_timen("Encountered exception in user's function.") 226 self.cleanup() 227 raise(func_exception) 228 gp.lprint_timen("Returned from the user's function.") 229 230 self.cleanup() 231 232 return result 233