diff --git a/changelogs/fragments/ansible-require-blocking-io.yml b/changelogs/fragments/ansible-require-blocking-io.yml new file mode 100644 index 00000000000..d32a83eedeb --- /dev/null +++ b/changelogs/fragments/ansible-require-blocking-io.yml @@ -0,0 +1,3 @@ +minor_changes: + - ansible - At startup the stdin/stdout/stderr file handles are checked to verify they are using blocking IO. + If not, the process exits with an error reporting which file handle(s) are using non-blocking IO. diff --git a/lib/ansible/cli/__init__.py b/lib/ansible/cli/__init__.py index 1e0b9628e02..731033fcf28 100644 --- a/lib/ansible/cli/__init__.py +++ b/lib/ansible/cli/__init__.py @@ -7,6 +7,7 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type +import os import sys # Used for determining if the system is running a new enough python version @@ -17,6 +18,28 @@ if sys.version_info < (3, 8): 'Current version: %s' % ''.join(sys.version.splitlines()) ) + +def check_blocking_io(): + """Check stdin/stdout/stderr to make sure they are using blocking IO.""" + handles = [] + + for handle in (sys.stdin, sys.stdout, sys.stderr): + # noinspection PyBroadException + try: + fd = handle.fileno() + except Exception: + continue # not a real file handle, such as during the import sanity test + + if not os.get_blocking(fd): + handles.append(getattr(handle, 'name', None) or '#%s' % fd) + + if handles: + raise SystemExit('ERROR: Ansible requires blocking IO on stdin/stdout/stderr. ' + 'Non-blocking file handles detected: %s' % ', '.join(_io for _io in handles)) + + +check_blocking_io() + from importlib.metadata import version from ansible.module_utils.compat.version import LooseVersion @@ -31,7 +54,6 @@ if jinja2_version < LooseVersion('3.0'): import errno import getpass -import os import subprocess import traceback from abc import ABC, abstractmethod