Skip to content

Commit

Permalink
[_614] functions within one priority level run last in, first out
Browse files Browse the repository at this point in the history
  • Loading branch information
d-w-moore committed Nov 17, 2024
1 parent 2507b06 commit b93cac2
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 27 deletions.
29 changes: 17 additions & 12 deletions irods/at_client_exit.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,42 @@
import enum
import logging


logger = logging.getLogger(__name__)

class priority(enum.Enum):
BEFORE_PRC = 10
DURING_PRC = 20
AFTER_PRC = 30
def __lt__(self,other):
return self.value < other.value

_cleanup_functions = {}

# So that we can access priority.ENUM_NAME as simply ENUM_NAME at module level.
for _ in priority.__members__:
exec('{}=priority.{}'.format(_,_))

_cleanup_functions = {BEFORE_PRC:[], DURING_PRC:[], AFTER_PRC:[]}

initialized = False

def register(priority_for_execution, function):
global initialized
if not initialized:
initialized = True
atexit.register(call_cleanup)
_cleanup_functions[function] = priority( priority_for_execution )

def call_cleanup():
funclist = sorted((pri.value,func) for func,pri in _cleanup_functions.items())
for priority,function in funclist:
try:
atexit.register(call_cleanup_functions)
array = _cleanup_functions[priority_for_execution]
if function not in array:
array.append(function)

def call_cleanup_functions():
ordered_funclists = sorted((pri,fl) for (pri,fl) in _cleanup_functions.items())
for priority,function_list in ordered_funclists:
for function in reversed(function_list):
# Ensure we execute all cleanup functions regardless of some of them raising exceptions.
function()
except Exception as exc:
logger.warning("%r raised from %s",exc,function)
try:
function()
except Exception as exc:
logger.warning("%r raised from %s",exc,function)


if __name__ == '__main__':
Expand Down
2 changes: 1 addition & 1 deletion irods/test/data_obj_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2106,7 +2106,7 @@ def test_proper_execution_of_client_exit_functions__issue_614(self):
# Assert that we have a null length string argument in the list
# We use an empty string record ("[]") to mark the point of the last Python statement executed in the script before cleanup begins.
self.assertGreater(len({_ for _ in args if _ == ""}), 0)
# Assert that this test factors in all constants ending in "_PRC" from the module.
# Assert that this test factors in all constants ending in "_PRC" from the module. These are assumed to be the stage names.
self.assertEqual({_ for _ in args if _.endswith("_PRC")},
{_ for _ in dir(irods.at_client_exit) if _.endswith("_PRC")})

Expand Down
34 changes: 20 additions & 14 deletions irods/test/modules/test_client_exit_functions.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import irods.at_client_exit
from irods.at_client_exit import register as register_prc_exit_function, BEFORE_PRC, DURING_PRC, AFTER_PRC
# Used in test of:
# irods.test.data_obj_test.TestDataObjOps.xxx
# irods.test.data_obj_test.TestDataObjOps.test_proper_execution_of_client_exit_functions__issue_614

from collections import OrderedDict
import sys

def get_stage_value_from_name(stage_name):
def get_stage_object_and_value_from_name(stage_name):
try:
return getattr(irods.at_client_exit,stage_name).value
obj = getattr(irods.at_client_exit, stage_name)
return obj, obj.value
except AttributeError:
return stage_name
return None, stage_name

def object_printer(stage_string = None,stream = sys.stdout):
return lambda: print(f'[{get_stage_value_from_name(stage_string)}]', end='', file = stream)
def object_printer(name, stream = sys.stdout):
(obj, value) = get_stage_object_and_value_from_name(stage_name = name)
return lambda: print(f'[{name if not obj else obj.value}]', end='', file = stream)

def projected_output_from_innate_list_order(name_list):
import io
Expand All @@ -25,11 +26,16 @@ def projected_output_from_innate_list_order(name_list):

if __name__ == '__main__':

dct = OrderedDict(([get_stage_value_from_name(key),object_printer(key)] for key in sys.argv[1:]))

for key, func in [(key,func) for key,func in dct.items() if key]:
register_prc_exit_function(key, func)
dct.pop(key)

for key, func in dct.items():
function_info = [[*get_stage_object_and_value_from_name(name),object_printer(name)] for name in reversed(sys.argv[1:])]

# For each argument to the script that represents a cleanup stage: register a function to print the stage's value, to be run during that stage.
for key in function_info.copy():
(stage,name,func) = key
if not stage:
continue
register_prc_exit_function(stage, func)
function_info.remove(key)

# Immediately execute any leftover functions, since the test expects the corresponding output to precede that of their counterparts run during cleanup.
for stage, name, func in function_info:
func()

0 comments on commit b93cac2

Please sign in to comment.