From 4c9e83f9f528f1899e4ede7c06b64053397e7a4b Mon Sep 17 00:00:00 2001 From: Toshio Kuratomi Date: Mon, 13 Aug 2018 18:17:50 -0700 Subject: [PATCH] [stable-2.5] Fix the local and ssh plugins for a cornercase retrying a syscall (#44074) The bundled selectors library which is used by the local and ssh connection plugins had a bug which caused a traceback in a cornercase. If selectors were in use and a syscall was interrupted, selectors would attempt to restart the syscall after the interrupt was processed. if the attempt determined that the timeout for running the syscall had already expired, the code attempted to raise OSError. The raise was using a Python3-ism and needed to be ported to work on Python2. Fixes #41630 (cherry picked from commit e2e44f8) Co-authored-by: Toshio Kuratomi --- .../fix-selectors-error-condition.yaml | 5 +++++ lib/ansible/compat/selectors/__init__.py | 8 ++++++++ lib/ansible/compat/selectors/_selectors2.py | 19 +++++++++++-------- 3 files changed, 24 insertions(+), 8 deletions(-) create mode 100644 changelogs/fragments/fix-selectors-error-condition.yaml diff --git a/changelogs/fragments/fix-selectors-error-condition.yaml b/changelogs/fragments/fix-selectors-error-condition.yaml new file mode 100644 index 00000000000..7ce618dde8b --- /dev/null +++ b/changelogs/fragments/fix-selectors-error-condition.yaml @@ -0,0 +1,5 @@ +--- +bugfixes: +- fix for the bundled selectors module (used in the ssh and local connection + plugins) when a syscall is restarted after being interrupted by a signal + (https://github.com/ansible/ansible/issues/41630) diff --git a/lib/ansible/compat/selectors/__init__.py b/lib/ansible/compat/selectors/__init__.py index 6458438d6dc..8d45ea5fbff 100644 --- a/lib/ansible/compat/selectors/__init__.py +++ b/lib/ansible/compat/selectors/__init__.py @@ -25,6 +25,14 @@ package exists on pypi to backport the functionality as far as python-2.6. ''' # The following makes it easier for us to script updates of the bundled code _BUNDLED_METADATA = {"pypi_name": "selectors2", "version": "1.1.0"} +# Added these bugfix commits from 2.1.0: +# * https://github.com/SethMichaelLarson/selectors2/commit/3bd74f2033363b606e1e849528ccaa76f5067590 +# Wrap kqueue.control so that timeout is a keyword arg +# * https://github.com/SethMichaelLarson/selectors2/commit/6f6a26f42086d8aab273b30be492beecb373646b +# Fix formatting of the kqueue.control patch for pylint +# * https://github.com/SethMichaelLarson/selectors2/commit/f0c2c6c66cfa7662bc52beaf4e2d65adfa25e189 +# Fix use of OSError exception for py3 and use the wrapper of kqueue.control so retries of +# interrupted syscalls work with kqueue import os.path import sys diff --git a/lib/ansible/compat/selectors/_selectors2.py b/lib/ansible/compat/selectors/_selectors2.py index a97b59e6630..bb5142c14cf 100644 --- a/lib/ansible/compat/selectors/_selectors2.py +++ b/lib/ansible/compat/selectors/_selectors2.py @@ -136,7 +136,7 @@ else: if expires is not None: current_time = monotonic() if current_time > expires: - raise OSError(errno=errno.ETIMEDOUT) + raise OSError(errno.ETIMEDOUT) if recalc_timeout: if "timeout" in kwargs: kwargs["timeout"] = expires - current_time @@ -340,7 +340,7 @@ if hasattr(select, "select"): timeout = None if timeout is None else max(timeout, 0.0) ready = [] r, w, _ = _syscall_wrapper(self._select, True, self._readers, - self._writers, timeout) + self._writers, timeout=timeout) r = set(r) w = set(w) for fd in r | w: @@ -561,14 +561,14 @@ if hasattr(select, "kqueue"): select.KQ_FILTER_READ, select.KQ_EV_ADD) - _syscall_wrapper(self._kqueue.control, False, [kevent], 0, 0) + _syscall_wrapper(self._wrap_control, False, [kevent], 0, 0) if events & EVENT_WRITE: kevent = select.kevent(key.fd, select.KQ_FILTER_WRITE, select.KQ_EV_ADD) - _syscall_wrapper(self._kqueue.control, False, [kevent], 0, 0) + _syscall_wrapper(self._wrap_control, False, [kevent], 0, 0) return key @@ -579,7 +579,7 @@ if hasattr(select, "kqueue"): select.KQ_FILTER_READ, select.KQ_EV_DELETE) try: - _syscall_wrapper(self._kqueue.control, False, [kevent], 0, 0) + _syscall_wrapper(self._wrap_control, False, [kevent], 0, 0) except SelectorError: pass if key.events & EVENT_WRITE: @@ -587,7 +587,7 @@ if hasattr(select, "kqueue"): select.KQ_FILTER_WRITE, select.KQ_EV_DELETE) try: - _syscall_wrapper(self._kqueue.control, False, [kevent], 0, 0) + _syscall_wrapper(self._wrap_control, False, [kevent], 0, 0) except SelectorError: pass @@ -600,8 +600,8 @@ if hasattr(select, "kqueue"): max_events = len(self._fd_to_key) * 2 ready_fds = {} - kevent_list = _syscall_wrapper(self._kqueue.control, True, - None, max_events, timeout) + kevent_list = _syscall_wrapper(self._wrap_control, True, + None, max_events, timeout=timeout) for kevent in kevent_list: fd = kevent.ident @@ -626,6 +626,9 @@ if hasattr(select, "kqueue"): self._kqueue.close() super(KqueueSelector, self).close() + def _wrap_control(self, changelist, max_events, timeout): + return self._kqueue.control(changelist, max_events, timeout) + __all__.append('KqueueSelector')