File prevent-logging-deadlock-on-salt-api-subprocesses-bs.patch of Package salt

From 4e6cc0c401bd6c9e47b324fc2df99e3bd679da33 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?=
 <psuarezhernandez@suse.com>
Date: Wed, 22 Jan 2020 08:19:55 +0000
Subject: [PATCH] Prevent logging deadlock on salt-api subprocesses
 (bsc#1159284)

---
 salt/_logging/impl.py         | 127 +++++++++++++++-------------------
 salt/client/ssh/__init__.py   |  12 +++-
 salt/client/ssh/client.py     |  21 +++---
 salt/client/ssh/wrapper/cp.py |  10 +--
 salt/loader.py                |   2 +-
 salt/utils/lazy.py            |  10 +--
 6 files changed, 85 insertions(+), 97 deletions(-)

diff --git a/salt/_logging/impl.py b/salt/_logging/impl.py
index 5a6292b445..2f15bf0025 100644
--- a/salt/_logging/impl.py
+++ b/salt/_logging/impl.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 """
     salt._logging.impl
     ~~~~~~~~~~~~~~~~~~
@@ -6,15 +5,12 @@
     Salt's logging implementation classes/functionality
 """
 
-# Import python libs
-from __future__ import absolute_import, print_function, unicode_literals
 
 import logging
 import re
 import sys
 import types
 
-# Import 3rd-party libs
 import salt.ext.six as six
 
 # Let's define these custom logging levels before importing the salt._logging.mixins
@@ -23,8 +19,8 @@ PROFILE = logging.PROFILE = 15
 TRACE = logging.TRACE = 5
 GARBAGE = logging.GARBAGE = 1
 QUIET = logging.QUIET = 1000
+DEBUG = logging.DEBUG = 10
 
-# Import Salt libs
 from salt._logging.handlers import StreamHandler  # isort:skip
 
 # from salt._logging.handlers import SysLogHandler  # isort:skip
@@ -52,7 +48,7 @@ LOG_LEVELS = {
     "warning": logging.WARNING,
 }
 
-LOG_VALUES_TO_LEVELS = dict((v, k) for (k, v) in LOG_LEVELS.items())
+LOG_VALUES_TO_LEVELS = {v: k for (k, v) in LOG_LEVELS.items()}
 
 LOG_COLORS = {
     "levels": {
@@ -96,9 +92,7 @@ LOG_COLORS = {
 }
 
 # Make a list of log level names sorted by log level
-SORTED_LEVEL_NAMES = [
-    l[0] for l in sorted(six.iteritems(LOG_LEVELS), key=lambda x: x[1])
-]
+SORTED_LEVEL_NAMES = [l[0] for l in sorted(LOG_LEVELS.items(), key=lambda x: x[1])]
 
 MODNAME_PATTERN = re.compile(r"(?P<name>%%\(name\)(?:\-(?P<digits>[\d]+))?s)")
 
@@ -168,8 +162,7 @@ def set_log_record_factory(factory):
     Set the logging  log record factory
     """
     get_log_record_factory.__factory__ = factory
-    if not six.PY2:
-        logging.setLogRecordFactory(factory)
+    logging.setLogRecordFactory(factory)
 
 
 set_log_record_factory(SaltLogRecord)
@@ -180,7 +173,7 @@ LOGGING_LOGGER_CLASS = logging.getLoggerClass()
 
 
 class SaltLoggingClass(
-    six.with_metaclass(LoggingMixinMeta, LOGGING_LOGGER_CLASS, NewStyleClassMixin)
+    LOGGING_LOGGER_CLASS, NewStyleClassMixin, metaclass=LoggingMixinMeta
 ):
     def __new__(cls, *args):
         """
@@ -194,13 +187,11 @@ class SaltLoggingClass(
             logging.getLogger(__name__)
 
         """
-        instance = super(SaltLoggingClass, cls).__new__(cls)
+        instance = super().__new__(cls)
 
-        try:
-            max_logger_length = len(
-                max(list(logging.Logger.manager.loggerDict), key=len)
-            )
-            for handler in logging.root.handlers:
+        max_logger_length = len(max(list(logging.Logger.manager.loggerDict), key=len))
+        for handler in logging.root.handlers:
+            try:
                 if handler in (
                     LOGGING_NULL_HANDLER,
                     LOGGING_STORE_HANDLER,
@@ -221,18 +212,15 @@ class SaltLoggingClass(
                 match = MODNAME_PATTERN.search(fmt)
                 if not match:
                     # Not matched. Release handler and return.
-                    handler.release()
                     return instance
 
                 if "digits" not in match.groupdict():
                     # No digits group. Release handler and return.
-                    handler.release()
                     return instance
 
                 digits = match.group("digits")
                 if not digits or not (digits and digits.isdigit()):
                     # No valid digits. Release handler and return.
-                    handler.release()
                     return instance
 
                 if int(digits) < max_logger_length:
@@ -243,9 +231,14 @@ class SaltLoggingClass(
                     )
                     handler.setFormatter(formatter)
                 handler.release()
-        except ValueError:
-            # There are no registered loggers yet
-            pass
+            except ValueError:
+                # There are no registered loggers yet
+                pass
+            finally:
+                try:
+                    handler.release()
+                except:
+                    pass
         return instance
 
     def _log(
@@ -279,7 +272,7 @@ class SaltLoggingClass(
                 "Only one of 'exc_info' and 'exc_info_on_loglevel' is " "permitted"
             )
         if exc_info_on_loglevel is not None:
-            if isinstance(exc_info_on_loglevel, six.string_types):
+            if isinstance(exc_info_on_loglevel, str):
                 exc_info_on_loglevel = LOG_LEVELS.get(
                     exc_info_on_loglevel, logging.ERROR
                 )
@@ -295,31 +288,37 @@ class SaltLoggingClass(
         else:
             extra["exc_info_on_loglevel"] = exc_info_on_loglevel
 
-        if sys.version_info < (3,):
-            LOGGING_LOGGER_CLASS._log(
-                self, level, msg, args, exc_info=exc_info, extra=extra
-            )
-        elif sys.version_info < (3, 8):
-            LOGGING_LOGGER_CLASS._log(
-                self,
-                level,
-                msg,
-                args,
-                exc_info=exc_info,
-                extra=extra,
-                stack_info=stack_info,
-            )
-        else:
-            LOGGING_LOGGER_CLASS._log(
-                self,
-                level,
-                msg,
-                args,
-                exc_info=exc_info,
-                extra=extra,
-                stack_info=stack_info,
-                stacklevel=stacklevel,
-            )
+        try:
+            logging._acquireLock()
+            if sys.version_info < (3,):
+                LOGGING_LOGGER_CLASS._log(
+                    self, level, msg, args, exc_info=exc_info, extra=extra
+                )
+            elif sys.version_info < (3, 8):
+                LOGGING_LOGGER_CLASS._log(
+                    self,
+                    level,
+                    msg,
+                    args,
+                    exc_info=exc_info,
+                    extra=extra,
+                    stack_info=stack_info,
+                )
+            else:
+                LOGGING_LOGGER_CLASS._log(
+                    self,
+                    level,
+                    msg,
+                    args,
+                    exc_info=exc_info,
+                    extra=extra,
+                    stack_info=stack_info,
+                    stacklevel=stacklevel,
+                )
+        except:
+            pass
+        finally:
+            logging._releaseLock()
 
     def makeRecord(
         self,
@@ -357,7 +356,7 @@ class SaltLoggingClass(
         except NameError:
             salt_system_encoding = "utf-8"
 
-        if isinstance(msg, six.string_types) and not isinstance(msg, six.text_type):
+        if isinstance(msg, str) and not isinstance(msg, str):
             try:
                 _msg = msg.decode(salt_system_encoding, "replace")
             except UnicodeDecodeError:
@@ -367,9 +366,7 @@ class SaltLoggingClass(
 
         _args = []
         for item in args:
-            if isinstance(item, six.string_types) and not isinstance(
-                item, six.text_type
-            ):
+            if isinstance(item, str) and not isinstance(item, str):
                 try:
                     _args.append(item.decode(salt_system_encoding, "replace"))
                 except UnicodeDecodeError:
@@ -378,24 +375,9 @@ class SaltLoggingClass(
                 _args.append(item)
         _args = tuple(_args)
 
-        if six.PY2:
-            # Recreate what's done for Py >= 3.5
-            _log_record_factory = get_log_record_factory()
-            logrecord = _log_record_factory(
-                name, level, fn, lno, _msg, _args, exc_info, func
-            )
-
-            if extra is not None:
-                for key in extra:
-                    if (key in ["message", "asctime"]) or (key in logrecord.__dict__):
-                        raise KeyError(
-                            "Attempt to overwrite '{}' in LogRecord".format(key)
-                        )
-                    logrecord.__dict__[key] = extra[key]
-        else:
-            logrecord = LOGGING_LOGGER_CLASS.makeRecord(
-                self, name, level, fn, lno, _msg, _args, exc_info, func, sinfo
-            )
+        logrecord = LOGGING_LOGGER_CLASS.makeRecord(
+            self, name, level, fn, lno, _msg, _args, exc_info, func, sinfo
+        )
 
         if exc_info_on_loglevel is not None:
             # Let's add some custom attributes to the LogRecord class in order
@@ -419,6 +401,7 @@ if logging.getLoggerClass() is not SaltLoggingClass:
     logging.addLevelName(PROFILE, "PROFILE")
     logging.addLevelName(TRACE, "TRACE")
     logging.addLevelName(GARBAGE, "GARBAGE")
+    logging.addLevelName(DEBUG, "DEBUG")
 
     # ----- REMOVE ON REFACTORING COMPLETE -------------------------------------------------------------------------->
     if not logging.root.handlers:
diff --git a/salt/client/ssh/__init__.py b/salt/client/ssh/__init__.py
index ef9eb0c07e..6570fba5b1 100644
--- a/salt/client/ssh/__init__.py
+++ b/salt/client/ssh/__init__.py
@@ -532,7 +532,9 @@ class SSH:
             **target
         )
         ret = {"id": single.id}
+        logging._acquireLock()
         stdout, stderr, retcode = single.run()
+        logging._releaseLock()
         # This job is done, yield
         try:
             data = salt.utils.json.find_json(stdout)
@@ -600,8 +602,14 @@ class SSH:
                     self.targets[host],
                     mine,
                 )
-                routine = Process(target=self.handle_routine, args=args)
-                routine.start()
+                try:
+                    logging._acquireLock()
+                    routine = Process(target=self.handle_routine, args=args)
+                    routine.start()
+                except:
+                    pass
+                finally:
+                    logging._releaseLock()
                 running[host] = {"thread": routine}
                 continue
             ret = {}
diff --git a/salt/client/ssh/client.py b/salt/client/ssh/client.py
index 55436366cf..d2dbdeb00e 100644
--- a/salt/client/ssh/client.py
+++ b/salt/client/ssh/client.py
@@ -1,14 +1,10 @@
-# -*- coding: utf-8 -*-
-
-# Import Python libs
-from __future__ import absolute_import, print_function, unicode_literals
-
 import copy
 import logging
+import multiprocessing
 import os
 import random
+import time
 
-# Import Salt libs
 import salt.config
 import salt.syspaths as syspaths
 import salt.utils.args
@@ -16,8 +12,10 @@ from salt.exceptions import SaltClientError  # Temporary
 
 log = logging.getLogger(__name__)
 
+_LOCK = multiprocessing.Lock()
 
-class SSHClient(object):
+
+class SSHClient:
     """
     Create a client object for executing routines via the salt-ssh backend
 
@@ -60,7 +58,11 @@ class SSHClient(object):
         opts["selected_target_option"] = tgt_type
         opts["tgt"] = tgt
         opts["arg"] = arg
-        return salt.client.ssh.SSH(opts)
+        _LOCK.acquire()
+        ret = salt.client.ssh.SSH(opts)
+        time.sleep(0.01)
+        _LOCK.release()
+        return ret
 
     def cmd_iter(
         self,
@@ -80,8 +82,7 @@ class SSHClient(object):
         .. versionadded:: 2015.5.0
         """
         ssh = self._prep_ssh(tgt, fun, arg, timeout, tgt_type, kwarg, **kwargs)
-        for ret in ssh.run_iter(jid=kwargs.get("jid", None)):
-            yield ret
+        yield from ssh.run_iter(jid=kwargs.get("jid", None))
 
     def cmd(
         self, tgt, fun, arg=(), timeout=None, tgt_type="glob", kwarg=None, **kwargs
diff --git a/salt/client/ssh/wrapper/cp.py b/salt/client/ssh/wrapper/cp.py
index e369d8475f..eb37ae971b 100644
--- a/salt/client/ssh/wrapper/cp.py
+++ b/salt/client/ssh/wrapper/cp.py
@@ -1,15 +1,11 @@
-# -*- coding: utf-8 -*-
 """
 Wrap the cp module allowing for managed ssh file transfers
 """
-# Import Python libs
-from __future__ import absolute_import, print_function
 
-import logging
 import os
 
-# Import salt libs
 import salt.client.ssh
+import salt.log.setup as logging
 import salt.utils.files
 import salt.utils.stringutils
 import salt.utils.templates
@@ -107,7 +103,7 @@ def _render_filenames(path, dest, saltenv, template):
     if template not in salt.utils.templates.TEMPLATE_REGISTRY:
         raise CommandExecutionError(
             "Attempted to render file paths with unavailable engine "
-            "{0}".format(template)
+            "{}".format(template)
         )
 
     kwargs = {}
@@ -133,7 +129,7 @@ def _render_filenames(path, dest, saltenv, template):
         if not data["result"]:
             # Failed to render the template
             raise CommandExecutionError(
-                "Failed to render file path with error: {0}".format(data["data"])
+                "Failed to render file path with error: {}".format(data["data"])
             )
         else:
             return data["data"]
diff --git a/salt/loader.py b/salt/loader.py
index 9a9c11b401..cfcd18f6f3 100644
--- a/salt/loader.py
+++ b/salt/loader.py
@@ -8,7 +8,6 @@ import functools
 import importlib.machinery  # pylint: disable=no-name-in-module,import-error
 import importlib.util  # pylint: disable=no-name-in-module,import-error
 import inspect
-import logging
 import os
 import re
 import sys
@@ -23,6 +22,7 @@ from zipimport import zipimporter
 import salt.config
 import salt.defaults.events
 import salt.defaults.exitcodes
+import salt.log.setup as logging
 import salt.syspaths
 import salt.utils.args
 import salt.utils.context
diff --git a/salt/utils/lazy.py b/salt/utils/lazy.py
index 8fc538164a..c828dd2c32 100644
--- a/salt/utils/lazy.py
+++ b/salt/utils/lazy.py
@@ -1,15 +1,13 @@
-# -*- coding: utf-8 -*-
 """
 Lazily-evaluated data structures, primarily used by Salt's loader
 """
 
-# Import Python Libs
-from __future__ import absolute_import, unicode_literals
 
-import logging
+import time
 from collections.abc import MutableMapping
 
 import salt.exceptions
+import salt.log.setup as logging
 
 log = logging.getLogger(__name__)
 
@@ -81,7 +79,7 @@ class LazyDict(MutableMapping):
 
         Override this to return a more meaningfull error message if possible
         """
-        return "'{0}' is not available.".format(function_name)
+        return "'{}' is not available.".format(function_name)
 
     def __setitem__(self, key, val):
         self._dict[key] = val
@@ -100,11 +98,13 @@ class LazyDict(MutableMapping):
             # load the item
             if self._load(key):
                 log.debug("LazyLoaded %s", key)
+                time.sleep(0.0001)
                 return self._dict[key]
             else:
                 log.debug(
                     "Could not LazyLoad %s: %s", key, self.missing_fun_string(key)
                 )
+                time.sleep(0.0001)
                 raise KeyError(key)
         else:
             return self._dict[key]
-- 
2.29.2
openSUSE Build Service is sponsored by