summaryrefslogtreecommitdiff
path: root/nova/utils.py
diff options
context:
space:
mode:
authorHervé Beraud <hberaud@redhat.com>2020-06-04 09:49:59 +0200
committermelanie witt <melwittt@gmail.com>2020-12-14 20:38:37 +0000
commit7c9edc02eda45aafbbb539b759e6b92f7aeb5ea8 (patch)
tree2969c036763ded63a4dd85fcf21279880ec11a6e /nova/utils.py
parent9edf181d4cb19b548d2882c448f5c029cf7f6616 (diff)
downloadnova-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.py46
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