diff options
author | Hervé Beraud <hberaud@redhat.com> | 2020-06-04 09:49:59 +0200 |
---|---|---|
committer | melanie witt <melwittt@gmail.com> | 2020-12-14 20:38:37 +0000 |
commit | 7c9edc02eda45aafbbb539b759e6b92f7aeb5ea8 (patch) | |
tree | 2969c036763ded63a4dd85fcf21279880ec11a6e /nova/utils.py | |
parent | 9edf181d4cb19b548d2882c448f5c029cf7f6616 (diff) | |
download | nova-7c9edc02eda45aafbbb539b759e6b92f7aeb5ea8.tar.gz |
Initialize global data separately and run_once in WSGI app init
We have discovered that if an exception is raised at any point during
the running of the init_application WSGI script in an apache/mod_wsgi
Daemon Mode environment, it will prompt apache/mod_wsgi to re-run the
script without starting a fresh python process. Because we initialize
global data structures during app init, subsequent runs of the script
blow up as some global data do *not* support re-initialization. It is
anyway not safe to assume that init of global data is safe to run
multiple times.
This mod_wsgi behavior appears to be a special situation that does not
behave the same as a normal reload in Daemon Mode as the script file is
being reloaded upon failure instead of the daemon process being
shutdown and restarted as described in the documentation [1].
In order to handle this situation, we can move the initialization of
global data structures to a helper method that is decorated to run only
once per python interpreter instance. This way, we will not attempt to
re-initialize global data that are not safe to init more than once.
Co-Authored-By: Michele Baldessari <michele@redhat.com>
Co-Authored-By: melanie witt <melwittt@gmail.com>
Closes-Bug: #1882094
[1] https://modwsgi.readthedocs.io/en/develop/user-guides/reloading-source-code.html#reloading-in-daemon-mode
Change-Id: I2bd360dcc6501feea7baf02d4510b282205fc061
Diffstat (limited to 'nova/utils.py')
-rw-r--r-- | nova/utils.py | 46 |
1 files changed, 46 insertions, 0 deletions
diff --git a/nova/utils.py b/nova/utils.py index 116bd49145..17dad8ffbf 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -1103,3 +1103,49 @@ def raise_if_old_compute(): scope=scope, min_service_level=current_service_version, oldest_supported_service=oldest_supported_service_level) + + +def run_once(message, logger, cleanup=None): + """This is a utility function decorator to ensure a function + is run once and only once in an interpreter instance. + + Note: this is copied from the placement repo (placement/util.py) + + The decorated function object can be reset by calling its + reset function. All exceptions raised by the wrapped function, + logger and cleanup function will be propagated to the caller. + """ + def outer_wrapper(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + if not wrapper.called: + # Note(sean-k-mooney): the called state is always + # updated even if the wrapped function completes + # by raising an exception. If the caller catches + # the exception it is their responsibility to call + # reset if they want to re-execute the wrapped function. + try: + return func(*args, **kwargs) + finally: + wrapper.called = True + else: + logger(message) + + wrapper.called = False + + def reset(wrapper, *args, **kwargs): + # Note(sean-k-mooney): we conditionally call the + # cleanup function if one is provided only when the + # wrapped function has been called previously. We catch + # and reraise any exception that may be raised and update + # the called state in a finally block to ensure its + # always updated if reset is called. + try: + if cleanup and wrapper.called: + return cleanup(*args, **kwargs) + finally: + wrapper.called = False + + wrapper.reset = functools.partial(reset, wrapper) + return wrapper + return outer_wrapper |