summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/ansible/executor/process/worker.py10
-rw-r--r--lib/ansible/utils/display.py29
2 files changed, 35 insertions, 4 deletions
diff --git a/lib/ansible/executor/process/worker.py b/lib/ansible/executor/process/worker.py
index d925864bbb..5113b83df2 100644
--- a/lib/ansible/executor/process/worker.py
+++ b/lib/ansible/executor/process/worker.py
@@ -87,10 +87,12 @@ class WorkerProcess(multiprocessing_context.Process): # type: ignore[name-defin
'''
self._save_stdin()
- try:
- return super(WorkerProcess, self).start()
- finally:
- self._new_stdin.close()
+ # FUTURE: this lock can be removed once a more generalized pre-fork thread pause is in place
+ with display._lock:
+ try:
+ return super(WorkerProcess, self).start()
+ finally:
+ self._new_stdin.close()
def _hard_exit(self, e):
'''
diff --git a/lib/ansible/utils/display.py b/lib/ansible/utils/display.py
index c3a5de98e2..e521f2a401 100644
--- a/lib/ansible/utils/display.py
+++ b/lib/ansible/utils/display.py
@@ -41,6 +41,7 @@ from ansible.utils.color import stringc
from ansible.utils.multiprocessing import context as multiprocessing_context
from ansible.utils.singleton import Singleton
from ansible.utils.unsafe_proxy import wrap_var
+from functools import wraps
_LIBC = ctypes.cdll.LoadLibrary(ctypes.util.find_library('c'))
@@ -163,12 +164,33 @@ b_COW_PATHS = (
)
+def _synchronize_textiowrapper(tio, lock):
+ # Ensure that a background thread can't hold the internal buffer lock on a file object
+ # during a fork, which causes forked children to hang. We're using display's existing lock for
+ # convenience (and entering the lock before a fork).
+ def _wrap_with_lock(f, lock):
+ @wraps(f)
+ def locking_wrapper(*args, **kwargs):
+ with lock:
+ return f(*args, **kwargs)
+
+ return locking_wrapper
+
+ buffer = tio.buffer
+
+ # monkeypatching the underlying file-like object isn't great, but likely safer than subclassing
+ buffer.write = _wrap_with_lock(buffer.write, lock)
+ buffer.flush = _wrap_with_lock(buffer.flush, lock)
+
+
class Display(metaclass=Singleton):
def __init__(self, verbosity=0):
self._final_q = None
+ # NB: this lock is used to both prevent intermingled output between threads and to block writes during forks.
+ # Do not change the type of this lock or upgrade to a shared lock (eg multiprocessing.RLock).
self._lock = threading.RLock()
self.columns = None
@@ -199,6 +221,13 @@ class Display(metaclass=Singleton):
self._set_column_width()
+ try:
+ # NB: we're relying on the display singleton behavior to ensure this only runs once
+ _synchronize_textiowrapper(sys.stdout, self._lock)
+ _synchronize_textiowrapper(sys.stderr, self._lock)
+ except Exception as ex:
+ self.warning(f"failed to patch stdout/stderr for fork-safety: {ex}")
+
def set_queue(self, queue):
"""Set the _final_q on Display, so that we know to proxy display over the queue
instead of directly writing to stdout/stderr from forks