|
| 1 | +import atexit |
| 2 | +import enum |
| 3 | +import logging |
| 4 | +import sys |
| 5 | +import threading |
| 6 | + |
| 7 | +logger = logging.getLogger(__name__) |
| 8 | + |
| 9 | +class LibraryCleanupStage(enum.Enum): |
| 10 | + # Integer value determines relative order of execution among the stages. |
| 11 | + BEFORE = 10 |
| 12 | + DURING = 20 |
| 13 | + AFTER = 30 |
| 14 | + def __lt__(self, other): |
| 15 | + return self.value < other.value |
| 16 | + |
| 17 | +def NOTIFY_VIA_ATTRIBUTE(func, stage): |
| 18 | + func.at_exit_stage = stage |
| 19 | + |
| 20 | +initialization_lock = threading.Lock() |
| 21 | +initialized = False |
| 22 | +_stage_notify = {} |
| 23 | + |
| 24 | +def _register(stage_for_execution, function, stage_notify_function = None): |
| 25 | + with initialization_lock: |
| 26 | + global initialized |
| 27 | + if not initialized: |
| 28 | + initialized = True |
| 29 | + atexit.register(_call_cleanup_functions) |
| 30 | + array = _cleanup_functions[stage_for_execution] |
| 31 | + if function not in array: |
| 32 | + array.append(function) |
| 33 | + _stage_notify[function] = stage_notify_function |
| 34 | + |
| 35 | +def _reset_cleanup_functions(): |
| 36 | + global _cleanup_functions |
| 37 | + _cleanup_functions = {LibraryCleanupStage.BEFORE:[], |
| 38 | + LibraryCleanupStage.DURING:[], |
| 39 | + LibraryCleanupStage.AFTER:[] |
| 40 | + } |
| 41 | + |
| 42 | +_reset_cleanup_functions() |
| 43 | + |
| 44 | +def _call_cleanup_functions(): |
| 45 | + ordered_funclists = sorted((pri,fl) for (pri,fl) in _cleanup_functions.items()) |
| 46 | + try: |
| 47 | + # Run each cleanup stage. |
| 48 | + for stage,function_list in ordered_funclists: |
| 49 | + # Within each cleanup stage, run all registered functions last-in, first-out. (Per atexit conventions.) |
| 50 | + for function in reversed(function_list): |
| 51 | + # Ensure we execute all cleanup functions regardless of some of them raising exceptions. |
| 52 | + try: |
| 53 | + notify = _stage_notify.get(function) |
| 54 | + if notify: |
| 55 | + notify(function, stage) |
| 56 | + function() |
| 57 | + except Exception as exc: |
| 58 | + logger.warning("%r raised from %s", exc, function) |
| 59 | + finally: |
| 60 | + _reset_cleanup_functions() |
| 61 | + |
| 62 | +# The primary interface: 'before' and 'after' are the execution order relative to the DURING stage, |
| 63 | +# in which the Python client cleans up its own session objects and runs the optional data object auto-close facility. |
| 64 | + |
| 65 | +def register_for_execution_before_prc_cleanup(function, **kw_args): |
| 66 | + """Register a function to be called by the Python iRODS Client (PRC) at exit time. |
| 67 | + It will be called without arguments, and before PRC begins its own cleanup tasks.""" |
| 68 | + return _register(LibraryCleanupStage.BEFORE, function, **kw_args) |
| 69 | + |
| 70 | +def register_for_execution_after_prc_cleanup(function, **kw_args): |
| 71 | + """Register a function to be called by the Python iRODS Client (PRC) at exit time. |
| 72 | + It will be called without arguments, and after PRC is done with its own cleanup tasks.""" |
| 73 | + return _register(LibraryCleanupStage.AFTER, function, **kw_args) |
| 74 | + |
| 75 | +class unique_function_invocation: |
| 76 | + """Wrap a function object to provide a unique handle for registration of a given |
| 77 | + function multiple times, possibly with different arguments. |
| 78 | + """ |
| 79 | + def __init__(self, func, tup_args = (), kw_args = ()): |
| 80 | + self.tup_args = tup_args |
| 81 | + self.kw_args = dict(kw_args) |
| 82 | + self.func = func |
| 83 | + self.at_exit_stage = None |
| 84 | + |
| 85 | + def __call__(self): |
| 86 | + return (self.func)(*self.tup_args, **self.kw_args) |
| 87 | + |
| 88 | +def get_stage(n = 2): |
| 89 | + """A utility function that can be called from within the highest call frame (use a higher |
| 90 | + "n" for subordinate frames) of a user-defined cleanup function to get the currently active |
| 91 | + cleanup stage, defined by a LibraryCleanupStage enum value. The user-defined function must have been |
| 92 | + wrapped by an instance of class unique_function_invocation, and that wrapper object |
| 93 | + registered using the keyword argument: stage_notify_function = NOTIFY_VIA_ATTRIBUTE.""" |
| 94 | + caller = sys._getframe(n).f_locals.get('self') |
| 95 | + if isinstance(caller, unique_function_invocation): |
| 96 | + return caller.at_exit_stage |
0 commit comments