diff options
author | Matt Davis <6775756+nitzmahone@users.noreply.github.com> | 2022-12-05 20:46:15 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-12-05 20:46:15 -0800 |
commit | 1424484be0e1b9a1d1e7e1849ae1a5e2a19d612c (patch) | |
tree | d14e1296fc4ecf6202d78b79e6262c0c1728c000 /test/lib/ansible_test/_util/target/sanity/import/importer.py | |
parent | 80d2f8da02052f64396da6b8caaf820eedbf18e2 (diff) | |
download | ansible-1424484be0e1b9a1d1e7e1849ae1a5e2a19d612c.tar.gz |
Prevent stdio deadlock in forked children (#79522)
* background threads writing to stdout/stderr can cause children to deadlock if a thread in the parent holds the internal lock on the BufferedWriter wrapper
* prevent writes to std handles during fork by monkeypatching stdout/stderr during display startup to require a mutex lock with fork(); this ensures no background threads can hold the lock during a fork operation
* add integration test that fails reliably on Linux without this fix
Diffstat (limited to 'test/lib/ansible_test/_util/target/sanity/import/importer.py')
-rw-r--r-- | test/lib/ansible_test/_util/target/sanity/import/importer.py | 24 |
1 files changed, 13 insertions, 11 deletions
diff --git a/test/lib/ansible_test/_util/target/sanity/import/importer.py b/test/lib/ansible_test/_util/target/sanity/import/importer.py index 3dcb8bf934..3180530c95 100644 --- a/test/lib/ansible_test/_util/target/sanity/import/importer.py +++ b/test/lib/ansible_test/_util/target/sanity/import/importer.py @@ -48,11 +48,7 @@ def main(): __import__(name) return sys.modules[name] - try: - # noinspection PyCompatibility - from StringIO import StringIO - except ImportError: - from io import StringIO + from io import BytesIO, TextIOWrapper try: from importlib.util import spec_from_loader, module_from_spec @@ -436,8 +432,9 @@ def main(): class Capture: """Captured output and/or exception.""" def __init__(self): - self.stdout = StringIO() - self.stderr = StringIO() + # use buffered IO to simulate StringIO; allows Ansible's stream patching to behave without warnings + self.stdout = TextIOWrapper(BytesIO()) + self.stderr = TextIOWrapper(BytesIO()) def capture_report(path, capture, messages): """Report on captured output. @@ -445,12 +442,17 @@ def main(): :type capture: Capture :type messages: set[str] """ - if capture.stdout.getvalue(): - first = capture.stdout.getvalue().strip().splitlines()[0].strip() + # since we're using buffered IO, flush before checking for data + capture.stdout.flush() + capture.stderr.flush() + stdout_value = capture.stdout.buffer.getvalue() + if stdout_value: + first = stdout_value.decode().strip().splitlines()[0].strip() report_message(path, 0, 0, 'stdout', first, messages) - if capture.stderr.getvalue(): - first = capture.stderr.getvalue().strip().splitlines()[0].strip() + stderr_value = capture.stderr.buffer.getvalue() + if stderr_value: + first = stderr_value.decode().strip().splitlines()[0].strip() report_message(path, 0, 0, 'stderr', first, messages) def report_message(path, line, column, code, message, messages): |