From 90df885832317d4bb8dc2e58be0f2777331bc10b Mon Sep 17 00:00:00 2001 From: Francois Granade Date: Sun, 22 Oct 2017 18:19:55 +0200 Subject: [PATCH 001/129] Support [include] in options files used by supervisorctl --- supervisor/options.py | 67 ++++++++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 30 deletions(-) diff --git a/supervisor/options.py b/supervisor/options.py index e4b19397e..bf59fecdc 100644 --- a/supervisor/options.py +++ b/supervisor/options.py @@ -393,6 +393,38 @@ def import_spec(self, spec): # this causes a DeprecationWarning on setuptools >= 11.3 return ep.load(False) + def read_include_config(self, fp, parser, expansions): + if parser.has_section('include'): + parser.expand_here(self.here) + if not parser.has_option('include', 'files'): + raise ValueError(".ini file has [include] section, but no " + "files setting") + files = parser.get('include', 'files') + files = expand(files, expansions, 'include.files') + files = files.split() + if hasattr(fp, 'name'): + base = os.path.dirname(os.path.abspath(fp.name)) + else: + base = '.' + for pattern in files: + pattern = os.path.join(base, pattern) + filenames = glob.glob(pattern) + if not filenames: + self.parse_warnings.append( + 'No file matches via include "%s"' % pattern) + continue + for filename in sorted(filenames): + if hasattr(self, 'parse_infos'): + self.parse_infos.append( + 'Included extra file "%s" during parsing' % filename) + try: + parser.read(filename) + except ConfigParser.ParsingError as why: + raise ValueError(str(why)) + else: + parser.expand_here( + os.path.abspath(os.path.dirname(filename)) + ) class ServerOptions(Options): user = None @@ -578,36 +610,8 @@ def read_config(self, fp): expansions = {'here':self.here, 'host_node_name':host_node_name} expansions.update(self.environ_expansions) - if parser.has_section('include'): - parser.expand_here(self.here) - if not parser.has_option('include', 'files'): - raise ValueError(".ini file has [include] section, but no " - "files setting") - files = parser.get('include', 'files') - files = expand(files, expansions, 'include.files') - files = files.split() - if hasattr(fp, 'name'): - base = os.path.dirname(os.path.abspath(fp.name)) - else: - base = '.' - for pattern in files: - pattern = os.path.join(base, pattern) - filenames = glob.glob(pattern) - if not filenames: - self.parse_warnings.append( - 'No file matches via include "%s"' % pattern) - continue - for filename in sorted(filenames): - self.parse_infos.append( - 'Included extra file "%s" during parsing' % filename) - try: - parser.read(filename) - except ConfigParser.ParsingError as why: - raise ValueError(str(why)) - else: - parser.expand_here( - os.path.abspath(os.path.dirname(filename)) - ) + + self.read_include_config(fp, parser, expansions) sections = parser.sections() if not 'supervisord' in sections: @@ -1656,8 +1660,11 @@ def read_config(self, fp): parser.read_file(fp) except AttributeError: parser.readfp(fp) + if need_close: fp.close() + self.read_include_config(fp, parser, parser.expansions) + sections = parser.sections() if not 'supervisorctl' in sections: raise ValueError('.ini file does not include supervisorctl section') From 8b6b45fbad358c937a040d1a16e9988661d58eea Mon Sep 17 00:00:00 2001 From: Francois Granade Date: Sun, 31 Dec 2017 17:49:47 +0100 Subject: [PATCH 002/129] Per review: Added tests, better error handling for [include]'d conf file in supervisorctl --- supervisor/options.py | 31 ++- .../tests/fixtures/example/included.conf | 3 + supervisor/tests/test_options.py | 243 +++++++++++------- 3 files changed, 169 insertions(+), 108 deletions(-) diff --git a/supervisor/options.py b/supervisor/options.py index bf59fecdc..b1c204ca7 100644 --- a/supervisor/options.py +++ b/supervisor/options.py @@ -102,6 +102,9 @@ def __init__(self, require_configfile=True): self.require_configfile = require_configfile self.add(None, None, "h", "help", self.help) self.add("configfile", None, "c:", "configuration=") + self.parse_criticals = [] + self.parse_warnings = [] + self.parse_infos = [] here = os.path.dirname(os.path.dirname(sys.argv[0])) searchpaths = [os.path.join(here, 'etc', 'supervisord.conf'), @@ -414,9 +417,8 @@ def read_include_config(self, fp, parser, expansions): 'No file matches via include "%s"' % pattern) continue for filename in sorted(filenames): - if hasattr(self, 'parse_infos'): - self.parse_infos.append( - 'Included extra file "%s" during parsing' % filename) + self.parse_infos.append( + 'Included extra file "%s" during parsing' % filename) try: parser.read(filename) except ConfigParser.ParsingError as why: @@ -426,6 +428,14 @@ def read_include_config(self, fp, parser, expansions): os.path.abspath(os.path.dirname(filename)) ) + def _log_parsing_messages(self): + for msg in self.parse_criticals: + logger.critical(msg) + for msg in self.parse_warnings: + logger.warn(msg) + for msg in self.parse_infos: + logger.info(msg) + class ServerOptions(Options): user = None sockchown = None @@ -481,9 +491,6 @@ def __init__(self): "", "profile_options=", profile_options, default=None) self.pidhistory = {} self.process_group_configs = [] - self.parse_criticals = [] - self.parse_warnings = [] - self.parse_infos = [] self.signal_receiver = SignalReceiver() self.poller = poller.Poller(self) @@ -1451,12 +1458,7 @@ def make_logger(self): maxbytes=self.logfile_maxbytes, backups=self.logfile_backups, ) - for msg in self.parse_criticals: - self.logger.critical(msg) - for msg in self.parse_warnings: - self.logger.warn(msg) - for msg in self.parse_infos: - self.logger.info(msg) + self._log_parsing_messages() def make_http_servers(self, supervisord): from supervisor.http import make_http_servers @@ -1640,6 +1642,11 @@ def realize(self, *arg, **kw): self.exit_on_error = 0 if self.interactive else 1 + format = '%(levelname)s: %(message)s\n' + self.logger = loggers.getLogger() + loggers.handle_stdout(self.logger, format) + self._log_parsing_messages() + def read_config(self, fp): section = self.configroot.supervisorctl need_close = False diff --git a/supervisor/tests/fixtures/example/included.conf b/supervisor/tests/fixtures/example/included.conf index 82b33cc2d..689c60557 100644 --- a/supervisor/tests/fixtures/example/included.conf +++ b/supervisor/tests/fixtures/example/included.conf @@ -1,2 +1,5 @@ [supervisord] childlogdir = %(here)s + +[supervisorctl] +history_file = %(here)s diff --git a/supervisor/tests/test_options.py b/supervisor/tests/test_options.py index ef6ffd3fb..ee1655914 100644 --- a/supervisor/tests/test_options.py +++ b/supervisor/tests/test_options.py @@ -257,7 +257,106 @@ def test_help(self): msg = 'A sample docstring for test_help\n' self.assertEqual(options.stdout.getvalue(), msg) -class ClientOptionsTests(unittest.TestCase): +class IncludeTestsMixin(object): + def test_read_config_include_with_no_files_raises_valueerror(self): + instance = self._makeOne() + text = lstrip("""\ + [supervisord] + + [include] + ;no files= + """) + try: + instance.read_config(StringIO(text)) + self.fail("nothing raised") + except ValueError as exc: + self.assertEqual(exc.args[0], + ".ini file has [include] section, but no files setting") + + def test_read_config_include_with_no_matching_files_logs_warning(self): + instance = self._makeOne() + text = lstrip("""\ + [supervisord] + + [supervisorctl] + + [include] + files=nonexistent/* + """) + instance.read_config(StringIO(text)) + self.assertEqual(instance.parse_warnings, + ['No file matches via include "./nonexistent/*"']) + + def test_read_config_include_reads_files_in_sorted_order(self): + dirname = tempfile.mkdtemp() + conf_d = os.path.join(dirname, "conf.d") + os.mkdir(conf_d) + + supervisord_conf = os.path.join(dirname, "supervisord.conf") + text = lstrip("""\ + [supervisord] + + [supervisorctl] + + [include] + files=%s/conf.d/*.conf + """ % dirname) + with open(supervisord_conf, 'w') as f: + f.write(text) + + from supervisor.compat import letters + a_z = letters[:26] + for letter in reversed(a_z): + filename = os.path.join(conf_d, "%s.conf" % letter) + with open(filename, "w") as f: + f.write("[program:%s]\n" + "command=/bin/%s\n" % (letter, letter)) + + instance = self._makeOne() + try: + instance.read_config(supervisord_conf) + finally: + shutil.rmtree(dirname, ignore_errors=True) + expected_msgs = [] + for letter in sorted(a_z): + filename = os.path.join(conf_d, "%s.conf" % letter) + expected_msgs.append( + 'Included extra file "%s" during parsing' % filename) + self.assertEqual(instance.parse_infos, expected_msgs) + + def test_read_config_include_extra_file_malformed(self): + dirname = tempfile.mkdtemp() + conf_d = os.path.join(dirname, "conf.d") + os.mkdir(conf_d) + + supervisord_conf = os.path.join(dirname, "supervisord.conf") + text = lstrip("""\ + [supervisord] + + [include] + files=%s/conf.d/*.conf + """ % dirname) + with open(supervisord_conf, 'w') as f: + f.write(text) + + malformed_file = os.path.join(conf_d, "a.conf") + with open(malformed_file, 'w') as f: + f.write("[inet_http_server]\njunk\n") + + instance = self._makeOne() + try: + instance.read_config(supervisord_conf) + self.fail("nothing raised") + except ValueError as exc: + self.assertTrue('contains parsing errors:' in exc.args[0]) + self.assertTrue(malformed_file in exc.args[0]) + msg = 'Included extra file "%s" during parsing' % malformed_file + self.assertTrue(msg in instance.parse_infos) + finally: + shutil.rmtree(dirname, ignore_errors=True) + + +class ClientOptionsTests(unittest.TestCase, IncludeTestsMixin): def _getTargetClass(self): from supervisor.options import ClientOptions return ClientOptions @@ -414,7 +513,53 @@ def test_options_unixsocket_configfile(self): instance.realize(args=[]) self.assertEqual(instance.serverurl, 'unix:///dev/null') -class ServerOptionsTests(unittest.TestCase): + def test_read_config_include_reads_extra_files(self): + dirname = tempfile.mkdtemp() + conf_d = os.path.join(dirname, "conf.d") + os.mkdir(conf_d) + + supervisord_conf = os.path.join(dirname, "supervisord.conf") + text = lstrip("""\ + [include] + files=%s/conf.d/*.conf %s/conf.d/*.ini + """ % (dirname, dirname)) + with open(supervisord_conf, 'w') as f: + f.write(text) + + conf_file = os.path.join(conf_d, "a.conf") + with open(conf_file, 'w') as f: + f.write("[supervisorctl]\nhistory_file=%(here)s/sc_history\n") + + ini_file = os.path.join(conf_d, "a.ini") + with open(ini_file, 'w') as f: + f.write("[supervisorctl]\nserverurl=unix://%(here)s/supervisord.sock\n") + + instance = self._makeOne() + try: + instance.read_config(supervisord_conf) + finally: + shutil.rmtree(dirname, ignore_errors=True) + options = instance.configroot.supervisorctl + # TODO self.assertEqual(len(options.server_configs), 2) + msg = 'Included extra file "%s" during parsing' % conf_file + self.assertTrue(msg in instance.parse_infos) + msg = 'Included extra file "%s" during parsing' % ini_file + self.assertTrue(msg in instance.parse_infos) + + def test_read_config_include_expands_here(self): + conf = os.path.join( + os.path.abspath(os.path.dirname(__file__)), 'fixtures', + 'include.conf') + root_here = os.path.dirname(conf) + include_here = os.path.join(root_here, 'example') + parser = self._makeOne() + parser.configfile = conf + parser.process_config_file(True) + section = parser.configroot.supervisorctl + self.assertEqual(section.history_file, include_here) + + +class ServerOptionsTests(unittest.TestCase, IncludeTestsMixin): def _getTargetClass(self): from supervisor.options import ServerOptions return ServerOptions @@ -837,33 +982,6 @@ def test_read_config_no_supervisord_section_raises_valueerror(self): self.assertEqual(exc.args[0], ".ini file does not include supervisord section") - def test_read_config_include_with_no_files_raises_valueerror(self): - instance = self._makeOne() - text = lstrip("""\ - [supervisord] - - [include] - ;no files= - """) - try: - instance.read_config(StringIO(text)) - self.fail("nothing raised") - except ValueError as exc: - self.assertEqual(exc.args[0], - ".ini file has [include] section, but no files setting") - - def test_read_config_include_with_no_matching_files_logs_warning(self): - instance = self._makeOne() - text = lstrip("""\ - [supervisord] - - [include] - files=nonexistent/* - """) - instance.read_config(StringIO(text)) - self.assertEqual(instance.parse_warnings, - ['No file matches via include "./nonexistent/*"']) - def test_read_config_include_reads_extra_files(self): dirname = tempfile.mkdtemp() conf_d = os.path.join(dirname, "conf.d") @@ -899,72 +1017,6 @@ def test_read_config_include_reads_extra_files(self): msg = 'Included extra file "%s" during parsing' % ini_file self.assertTrue(msg in instance.parse_infos) - def test_read_config_include_reads_files_in_sorted_order(self): - dirname = tempfile.mkdtemp() - conf_d = os.path.join(dirname, "conf.d") - os.mkdir(conf_d) - - supervisord_conf = os.path.join(dirname, "supervisord.conf") - text = lstrip("""\ - [supervisord] - - [include] - files=%s/conf.d/*.conf - """ % dirname) - with open(supervisord_conf, 'w') as f: - f.write(text) - - from supervisor.compat import letters - a_z = letters[:26] - for letter in reversed(a_z): - filename = os.path.join(conf_d, "%s.conf" % letter) - with open(filename, "w") as f: - f.write("[program:%s]\n" - "command=/bin/%s\n" % (letter, letter)) - - instance = self._makeOne() - try: - instance.read_config(supervisord_conf) - finally: - shutil.rmtree(dirname, ignore_errors=True) - expected_msgs = [] - for letter in sorted(a_z): - filename = os.path.join(conf_d, "%s.conf" % letter) - expected_msgs.append( - 'Included extra file "%s" during parsing' % filename) - self.assertEqual(instance.parse_infos, expected_msgs) - - def test_read_config_include_extra_file_malformed(self): - dirname = tempfile.mkdtemp() - conf_d = os.path.join(dirname, "conf.d") - os.mkdir(conf_d) - - supervisord_conf = os.path.join(dirname, "supervisord.conf") - text = lstrip("""\ - [supervisord] - - [include] - files=%s/conf.d/*.conf - """ % dirname) - with open(supervisord_conf, 'w') as f: - f.write(text) - - malformed_file = os.path.join(conf_d, "a.conf") - with open(malformed_file, 'w') as f: - f.write("[inet_http_server]\njunk\n") - - instance = self._makeOne() - try: - instance.read_config(supervisord_conf) - self.fail("nothing raised") - except ValueError as exc: - self.assertTrue('contains parsing errors:' in exc.args[0]) - self.assertTrue(malformed_file in exc.args[0]) - msg = 'Included extra file "%s" during parsing' % malformed_file - self.assertTrue(msg in instance.parse_infos) - finally: - shutil.rmtree(dirname, ignore_errors=True) - def test_read_config_include_expands_host_node_name(self): dirname = tempfile.mkdtemp() conf_d = os.path.join(dirname, "conf.d") @@ -3502,4 +3554,3 @@ def test_suite(): if __name__ == '__main__': unittest.main(defaultTest='test_suite') - From a039f5829ca20d633618ef731d28ff81d04a5193 Mon Sep 17 00:00:00 2001 From: Francois Granade Date: Sun, 31 Dec 2017 17:59:25 +0100 Subject: [PATCH 003/129] A little more testing --- supervisor/tests/test_options.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/supervisor/tests/test_options.py b/supervisor/tests/test_options.py index ee1655914..e58798d27 100644 --- a/supervisor/tests/test_options.py +++ b/supervisor/tests/test_options.py @@ -540,7 +540,9 @@ def test_read_config_include_reads_extra_files(self): finally: shutil.rmtree(dirname, ignore_errors=True) options = instance.configroot.supervisorctl - # TODO self.assertEqual(len(options.server_configs), 2) + history_file = os.path.join(conf_d, 'sc_history') + self.assertEqual(options.serverurl, 'unix://' + conf_d + '/supervisord.sock') + self.assertEqual(options.history_file, history_file) msg = 'Included extra file "%s" during parsing' % conf_file self.assertTrue(msg in instance.parse_infos) msg = 'Included extra file "%s" during parsing' % ini_file From 5b72d1b744241b3f8b503cf1cf4f13fef939ecb9 Mon Sep 17 00:00:00 2001 From: Francois Granade Date: Sun, 31 Dec 2017 18:03:01 +0100 Subject: [PATCH 004/129] typo causing tests to fail --- supervisor/options.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/supervisor/options.py b/supervisor/options.py index b1c204ca7..18a84d6be 100644 --- a/supervisor/options.py +++ b/supervisor/options.py @@ -430,11 +430,11 @@ def read_include_config(self, fp, parser, expansions): def _log_parsing_messages(self): for msg in self.parse_criticals: - logger.critical(msg) + self.logger.critical(msg) for msg in self.parse_warnings: - logger.warn(msg) + self.logger.warn(msg) for msg in self.parse_infos: - logger.info(msg) + self.logger.info(msg) class ServerOptions(Options): user = None From bfde5eb4ac3b77c4e640187806d7abae66188b40 Mon Sep 17 00:00:00 2001 From: Francois Granade Date: Sun, 31 Dec 2017 18:05:55 +0100 Subject: [PATCH 005/129] Better fix for the previous typo --- supervisor/options.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/supervisor/options.py b/supervisor/options.py index 18a84d6be..0562804ec 100644 --- a/supervisor/options.py +++ b/supervisor/options.py @@ -428,13 +428,13 @@ def read_include_config(self, fp, parser, expansions): os.path.abspath(os.path.dirname(filename)) ) - def _log_parsing_messages(self): + def _log_parsing_messages(self, logger): for msg in self.parse_criticals: - self.logger.critical(msg) + logger.critical(msg) for msg in self.parse_warnings: - self.logger.warn(msg) + logger.warn(msg) for msg in self.parse_infos: - self.logger.info(msg) + logger.info(msg) class ServerOptions(Options): user = None @@ -1458,7 +1458,7 @@ def make_logger(self): maxbytes=self.logfile_maxbytes, backups=self.logfile_backups, ) - self._log_parsing_messages() + self._log_parsing_messages(self.logger) def make_http_servers(self, supervisord): from supervisor.http import make_http_servers @@ -1643,9 +1643,9 @@ def realize(self, *arg, **kw): self.exit_on_error = 0 if self.interactive else 1 format = '%(levelname)s: %(message)s\n' - self.logger = loggers.getLogger() - loggers.handle_stdout(self.logger, format) - self._log_parsing_messages() + logger = loggers.getLogger() + loggers.handle_stdout(logger, format) + self._log_parsing_messages(logger) def read_config(self, fp): section = self.configroot.supervisorctl From c16e1019be9dca3e4f7c28192711687f73e91e5b Mon Sep 17 00:00:00 2001 From: Francois Granade Date: Sun, 31 Dec 2017 18:22:01 +0100 Subject: [PATCH 006/129] Removed the note in the doc saying that supervisorctl ignores [include] --- docs/configuration.rst | 6 ------ 1 file changed, 6 deletions(-) diff --git a/docs/configuration.rst b/docs/configuration.rst index 5df0e5d5f..970d239d6 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -1117,12 +1117,6 @@ section, it must contain a single key named "files". The values in this key specify other configuration files to be included within the configuration. -.. note:: - - The ``[include]`` section is processed only by ``supervisord``. It is - ignored by ``supervisorctl``. - - ``[include]`` Section Values ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From 95717e79b2be496543ec2b51d7318a85e6644682 Mon Sep 17 00:00:00 2001 From: Vinay Sajip Date: Sun, 10 May 2020 09:57:20 +0100 Subject: [PATCH 007/129] First cut at fix for #1273. --- supervisor/web.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/supervisor/web.py b/supervisor/web.py index ee1568165..926e8d43f 100644 --- a/supervisor/web.py +++ b/supervisor/web.py @@ -8,6 +8,7 @@ from supervisor.compat import urllib from supervisor.compat import urlparse +from supervisor.compat import as_bytes from supervisor.compat import as_string from supervisor.compat import PY2 from supervisor.compat import unicode @@ -179,7 +180,7 @@ def __call__(self): headers['Pragma'] = 'no-cache' headers['Cache-Control'] = 'no-cache' headers['Expires'] = http_date.build_http_date(0) - response['body'] = as_string(body) + response['body'] = as_bytes(body) return response def render(self): From c2d474ef2f926ab04d5f771a6b72214810001482 Mon Sep 17 00:00:00 2001 From: leeprecy Date: Mon, 23 Nov 2020 13:09:32 -0800 Subject: [PATCH 008/129] Enhancement on supervisord log severity on process exit status --- supervisor/process.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/supervisor/process.py b/supervisor/process.py index c084e68bd..aa76150ca 100644 --- a/supervisor/process.py +++ b/supervisor/process.py @@ -549,6 +549,14 @@ def finish(self, pid, sts): msg = "stopped: %s (%s)" % (processname, msg) self._assertInState(ProcessStates.STOPPING) self.change_state(ProcessStates.STOPPED) + if exit_expected: + self.config.options.logger.info(msg) + else: + if "SIGTERM" in msg or "SIGKILL" in msg: + self.config.options.logger.warn(msg) + else: + self.config.options.logger.critical(msg) + elif too_quickly: # the program did not stay up long enough to make it to RUNNING @@ -558,6 +566,7 @@ def finish(self, pid, sts): msg = "exited: %s (%s)" % (processname, msg + "; not expected") self._assertInState(ProcessStates.STARTING) self.change_state(ProcessStates.BACKOFF) + self.config.options.logger.warn(msg) else: # this finish was not the result of a stop request, the @@ -579,13 +588,16 @@ def finish(self, pid, sts): # expected exit code msg = "exited: %s (%s)" % (processname, msg + "; expected") self.change_state(ProcessStates.EXITED, expected=True) + self.config.options.logger.info(msg) else: # unexpected exit code self.spawnerr = 'Bad exit code %s' % es msg = "exited: %s (%s)" % (processname, msg + "; not expected") self.change_state(ProcessStates.EXITED, expected=False) - - self.config.options.logger.info(msg) + if "SIGTERM" in msg or "SIGKILL" in msg: + self.config.options.logger.warn(msg) + else: + self.config.options.logger.critical(msg) self.pid = 0 self.config.options.close_parent_pipes(self.pipes) From 2b1e8ef18e6f51e8408b4f5282403464222c0954 Mon Sep 17 00:00:00 2001 From: leeprecy Date: Mon, 23 Nov 2020 13:09:32 -0800 Subject: [PATCH 009/129] Enhancement on supervisord log severity on process exit status --- supervisor/process.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/supervisor/process.py b/supervisor/process.py index c084e68bd..06a8e5d8c 100644 --- a/supervisor/process.py +++ b/supervisor/process.py @@ -549,6 +549,11 @@ def finish(self, pid, sts): msg = "stopped: %s (%s)" % (processname, msg) self._assertInState(ProcessStates.STOPPING) self.change_state(ProcessStates.STOPPED) + if exit_expected: + self.config.options.logger.info(msg) + else: + self.config.options.logger.warn(msg) + elif too_quickly: # the program did not stay up long enough to make it to RUNNING @@ -558,6 +563,7 @@ def finish(self, pid, sts): msg = "exited: %s (%s)" % (processname, msg + "; not expected") self._assertInState(ProcessStates.STARTING) self.change_state(ProcessStates.BACKOFF) + self.config.options.logger.warn(msg) else: # this finish was not the result of a stop request, the @@ -579,13 +585,13 @@ def finish(self, pid, sts): # expected exit code msg = "exited: %s (%s)" % (processname, msg + "; expected") self.change_state(ProcessStates.EXITED, expected=True) + self.config.options.logger.info(msg) else: # unexpected exit code self.spawnerr = 'Bad exit code %s' % es msg = "exited: %s (%s)" % (processname, msg + "; not expected") self.change_state(ProcessStates.EXITED, expected=False) - - self.config.options.logger.info(msg) + self.config.options.logger.warn(msg) self.pid = 0 self.config.options.close_parent_pipes(self.pipes) From 9140aad743c06a843b608359128287382164380e Mon Sep 17 00:00:00 2001 From: yellmean Date: Sat, 20 Feb 2021 09:47:42 +0800 Subject: [PATCH 010/129] Update rpcinterface.py Add two configuration properties to the getAllConfigInfo method --- supervisor/rpcinterface.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/supervisor/rpcinterface.py b/supervisor/rpcinterface.py index 5b6d19a2b..c6a9fa880 100644 --- a/supervisor/rpcinterface.py +++ b/supervisor/rpcinterface.py @@ -563,6 +563,7 @@ def getAllConfigInfo(self): inuse = gconfig.name in self.supervisord.process_groups for pconfig in gconfig.process_configs: d = {'autostart': pconfig.autostart, + 'directory': pconfig.directory, 'command': pconfig.command, 'exitcodes': pconfig.exitcodes, 'group': gconfig.name, @@ -588,6 +589,7 @@ def getAllConfigInfo(self): 'stderr_logfile_backups': pconfig.stderr_logfile_backups, 'stderr_logfile_maxbytes': pconfig.stderr_logfile_maxbytes, 'stderr_syslog': pconfig.stderr_syslog, + 'server_url': pconfig.server_url, } # no support for these types in xml-rpc d.update((k, 'auto') for k, v in d.items() if v is Automatic) From 1945006d1226631df00d380764c1bdebd3565a5c Mon Sep 17 00:00:00 2001 From: yellmean Date: Sat, 20 Feb 2021 11:09:39 +0800 Subject: [PATCH 011/129] Update rpcinterface.py --- supervisor/rpcinterface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/supervisor/rpcinterface.py b/supervisor/rpcinterface.py index c6a9fa880..60e61dd9b 100644 --- a/supervisor/rpcinterface.py +++ b/supervisor/rpcinterface.py @@ -589,7 +589,7 @@ def getAllConfigInfo(self): 'stderr_logfile_backups': pconfig.stderr_logfile_backups, 'stderr_logfile_maxbytes': pconfig.stderr_logfile_maxbytes, 'stderr_syslog': pconfig.stderr_syslog, - 'server_url': pconfig.server_url, + 'serverurl': pconfig.serverurl, } # no support for these types in xml-rpc d.update((k, 'auto') for k, v in d.items() if v is Automatic) From 9f030973ec283d3d392bb2614733ecfae2dc4dcb Mon Sep 17 00:00:00 2001 From: yellmean Date: Sun, 21 Feb 2021 13:20:47 +0800 Subject: [PATCH 012/129] Update rpcinterface.py --- supervisor/rpcinterface.py | 1 + 1 file changed, 1 insertion(+) diff --git a/supervisor/rpcinterface.py b/supervisor/rpcinterface.py index 60e61dd9b..16516de52 100644 --- a/supervisor/rpcinterface.py +++ b/supervisor/rpcinterface.py @@ -564,6 +564,7 @@ def getAllConfigInfo(self): for pconfig in gconfig.process_configs: d = {'autostart': pconfig.autostart, 'directory': pconfig.directory, + 'uid': pconfig.uid, 'command': pconfig.command, 'exitcodes': pconfig.exitcodes, 'group': gconfig.name, From d72762e825a1d00c31c740011c4018989241e294 Mon Sep 17 00:00:00 2001 From: Louis Sautier Date: Thu, 16 Sep 2021 13:02:00 +0200 Subject: [PATCH 013/129] setup.py: add setuptools to install_requires pkg_resource is used by several installed files. --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index dd87f7d06..6cdc31d4d 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,8 @@ elif (3, 0) < py_version < (3, 4): raise RuntimeError('On Python 3, Supervisor requires Python 3.4 or later') -requires = [] +# pkg_resource is used in several places +requires = ["setuptools"] tests_require = [] if py_version < (3, 3): tests_require.append('mock<4.0.0.dev0') From 280d0bfd2f8e0132c7a3fed208f1feacd8fc58ba Mon Sep 17 00:00:00 2001 From: Dalton Santos Date: Thu, 30 Sep 2021 17:23:50 -0300 Subject: [PATCH 014/129] Fix "numprocs_start" description --- docs/configuration.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.rst b/docs/configuration.rst index 129ce0cc1..84f8c8c3e 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -695,7 +695,7 @@ where specified. ``numprocs_start`` An integer offset that is used to compute the number at which - ``numprocs`` starts. + ``process_num`` starts. *Default*: 0 From de13d0efaae0aeb755b56643163ec101f5dc21ad Mon Sep 17 00:00:00 2001 From: Iago Alonso Date: Thu, 7 Oct 2021 20:08:24 +0200 Subject: [PATCH 015/129] Formatting --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 6cdc31d4d..b60c7ae6a 100644 --- a/setup.py +++ b/setup.py @@ -31,7 +31,7 @@ testing_extras = tests_require + [ 'pytest', 'pytest-cov', - ] +] from setuptools import setup, find_packages here = os.path.abspath(os.path.dirname(__file__)) @@ -81,7 +81,7 @@ install_requires=requires, extras_require={ 'testing': testing_extras, - }, + }, tests_require=tests_require, include_package_data=True, zip_safe=False, From 557c0c5619ec70f6cec3e430c1a44aaf38cd6c78 Mon Sep 17 00:00:00 2001 From: Iago Alonso Date: Thu, 7 Oct 2021 20:09:37 +0200 Subject: [PATCH 016/129] Do not use bare except --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b60c7ae6a..4dca6517c 100644 --- a/setup.py +++ b/setup.py @@ -38,7 +38,7 @@ try: README = open(os.path.join(here, 'README.rst')).read() CHANGES = open(os.path.join(here, 'CHANGES.rst')).read() -except: +except Exception: README = """\ Supervisor is a client/server system that allows its users to control a number of processes on UNIX-like operating systems. """ From e599290f2c4f5880e950a2d352d8fa24b2095a91 Mon Sep 17 00:00:00 2001 From: Iago Alonso Date: Thu, 7 Oct 2021 20:11:06 +0200 Subject: [PATCH 017/129] Use context managers so we don't leak file descriptors --- setup.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 4dca6517c..de4a0e394 100644 --- a/setup.py +++ b/setup.py @@ -36,8 +36,10 @@ from setuptools import setup, find_packages here = os.path.abspath(os.path.dirname(__file__)) try: - README = open(os.path.join(here, 'README.rst')).read() - CHANGES = open(os.path.join(here, 'CHANGES.rst')).read() + with open(os.path.join(here, 'README.rst'), 'r') as f: + README = f.read() + with open(os.path.join(here, 'CHANGES.rst'), 'r') as f: + CHANGES = f.read() except Exception: README = """\ Supervisor is a client/server system that allows its users to @@ -65,7 +67,8 @@ ] version_txt = os.path.join(here, 'supervisor/version.txt') -supervisor_version = open(version_txt).read().strip() +with open(version_txt, 'r') as f: + supervisor_version = f.read().strip() dist = setup( name='supervisor', From 3d5654e8c8cd4bf16721183964768a17b368aa16 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Thu, 7 Oct 2021 22:56:58 -0700 Subject: [PATCH 018/129] Revert "Formatting" This reverts commit de13d0efaae0aeb755b56643163ec101f5dc21ad. --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index de4a0e394..fb6a31d1e 100644 --- a/setup.py +++ b/setup.py @@ -31,7 +31,7 @@ testing_extras = tests_require + [ 'pytest', 'pytest-cov', -] + ] from setuptools import setup, find_packages here = os.path.abspath(os.path.dirname(__file__)) @@ -84,7 +84,7 @@ install_requires=requires, extras_require={ 'testing': testing_extras, - }, + }, tests_require=tests_require, include_package_data=True, zip_safe=False, From f84fc3e62d332ba74d9bcf9b808fea97352df2a9 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Thu, 7 Oct 2021 23:01:51 -0700 Subject: [PATCH 019/129] Add changelog entry for 4d7ef57e929d3d1064e952f86f82454cc288b190 --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 8b64b408b..f3ef80b8a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,9 @@ 4.3.0.dev0 (Next Release) ------------------------- +- Added the ``setuptools`` package to the list of dependencies in + ``setup.py`` because it is a runtime dependency. Patch by Louis Sautier. + - The web interface will now return a 404 Not Found response if a log file is missing. Previously, it would return 410 Gone. It was changed because 410 is intended to mean that the condition is likely to be permanent. A From dc77f645de11cd568c81428c3f37bfd405bdc616 Mon Sep 17 00:00:00 2001 From: Chao Wang Date: Tue, 2 Nov 2021 16:25:11 -0500 Subject: [PATCH 020/129] #1457 update process state before notify events --- supervisor/process.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/supervisor/process.py b/supervisor/process.py index d6f60f3e2..075204a06 100644 --- a/supervisor/process.py +++ b/supervisor/process.py @@ -166,6 +166,8 @@ def change_state(self, new_state, expected=True): return False event_class = self.event_map.get(new_state) + self.state = new_state + if event_class is not None: event = event_class(self, old_state, expected) events.notify(event) @@ -175,8 +177,6 @@ def change_state(self, new_state, expected=True): self.backoff += 1 self.delay = now + self.backoff - self.state = new_state - def _assertInState(self, *states): if self.state not in states: current_state = getProcessStateDescription(self.state) From d0f3a40e85e9c3a8b6b937f8366d6855ecca177a Mon Sep 17 00:00:00 2001 From: Aliaksandr Veramkovich <37269131+Veramkovich@users.noreply.github.com> Date: Wed, 3 Nov 2021 18:16:27 +0300 Subject: [PATCH 021/129] Update datatypes.py Code change to support IPv6 address format. print(inet_address('2001:db8:ff:55:0:0:0:138:9001')) print(inet_address('127.0.0.1:9001')) --- supervisor/datatypes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/supervisor/datatypes.py b/supervisor/datatypes.py index 8de9ce5c4..5a8994c48 100644 --- a/supervisor/datatypes.py +++ b/supervisor/datatypes.py @@ -135,7 +135,7 @@ def inet_address(s): # returns (host, port) tuple host = '' if ":" in s: - host, s = s.split(":", 1) + host, s = s.rsplit(":", 1) if not s: raise ValueError("no port number specified in %r" % s) port = port_number(s) From 52e29f64d2fd0d25769acfa53b32c804870d450d Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Wed, 3 Nov 2021 17:17:28 -0700 Subject: [PATCH 022/129] Add test for stopsignal= with a number. Refs #1466 --- supervisor/tests/test_options.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/supervisor/tests/test_options.py b/supervisor/tests/test_options.py index 5c9a35f6d..a80deb08f 100644 --- a/supervisor/tests/test_options.py +++ b/supervisor/tests/test_options.py @@ -1844,6 +1844,20 @@ def test_processes_from_section_redirect_stderr_with_auto(self): self.assertEqual(instance.parse_warnings, []) self.assertEqual(pconfigs[0].stderr_logfile, None) + def test_processes_from_section_accepts_number_for_stopsignal(self): + instance = self._makeOne() + text = lstrip("""\ + [program:foo] + command = /bin/foo + stopsignal = %d + """ % signal.SIGQUIT) + from supervisor.options import UnhosedConfigParser + config = UnhosedConfigParser() + config.read_string(text) + pconfigs = instance.processes_from_section(config, 'program:foo', 'bar') + self.assertEqual(instance.parse_warnings, []) + self.assertEqual(pconfigs[0].stopsignal, signal.SIGQUIT) + def test_options_with_environment_expansions(self): text = lstrip("""\ [supervisord] From 132d4de6a39e5483fb99ba0b8f03039a571d42a7 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Wed, 3 Nov 2021 17:23:35 -0700 Subject: [PATCH 023/129] Document that signal can be specified by number. Refs #1466 --- docs/configuration.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/configuration.rst b/docs/configuration.rst index 84f8c8c3e..a708621df 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -817,8 +817,9 @@ where specified. ``stopsignal`` - The signal used to kill the program when a stop is requested. This - can be any of TERM, HUP, INT, QUIT, KILL, USR1, or USR2. + The signal used to kill the program when a stop is requested. This can be + specified using the signal's name or its number. It is normally one of: + ``TERM``, ``HUP``, ``INT``, ``QUIT``, ``KILL``, ``USR1``, or ``USR2``. *Default*: TERM From 464aceee49c3caee489b7b596300dcbb30481936 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Wed, 3 Nov 2021 17:44:15 -0700 Subject: [PATCH 024/129] Add test for d0f3a40e85e9c3a8b6b937f8366d6855ecca177a --- supervisor/tests/test_datatypes.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/supervisor/tests/test_datatypes.py b/supervisor/tests/test_datatypes.py index ff2c966f3..4f610d1b4 100644 --- a/supervisor/tests/test_datatypes.py +++ b/supervisor/tests/test_datatypes.py @@ -632,14 +632,24 @@ def test_bad_port_number(self): self.assertRaises(ValueError, self._callFUT, 'a') def test_default_host(self): - host, port = self._callFUT('*:8080') + host, port = self._callFUT('*:9001') self.assertEqual(host, '') - self.assertEqual(port, 8080) + self.assertEqual(port, 9001) - def test_boring(self): - host, port = self._callFUT('localhost:80') + def test_hostname_and_port(self): + host, port = self._callFUT('localhost:9001') self.assertEqual(host, 'localhost') - self.assertEqual(port, 80) + self.assertEqual(port, 9001) + + def test_ipv4_address_and_port(self): + host, port = self._callFUT('127.0.0.1:9001') + self.assertEqual(host, '127.0.0.1') + self.assertEqual(port, 9001) + + def test_ipv6_address_and_port(self): + host, port = self._callFUT('2001:db8:ff:55:0:0:0:138:9001') + self.assertEqual(host, '2001:db8:ff:55:0:0:0:138') + self.assertEqual(port, 9001) class SocketAddressTests(unittest.TestCase): def _getTargetClass(self): From 22728420260317e71fa88a478e731a4bcb942920 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Wed, 3 Nov 2021 19:48:12 -0700 Subject: [PATCH 025/129] Update process attributes before emitting event Closes #1457 Closes #1468 --- CHANGES.rst | 5 ++ supervisor/process.py | 11 ++- supervisor/tests/test_process.py | 136 +++++++++++++++++++------------ 3 files changed, 95 insertions(+), 57 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index f3ef80b8a..388e4a64a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,11 @@ 4.3.0.dev0 (Next Release) ------------------------- +- Fixed a race condition where an ``rpcinterface`` extension that subscribed + to events would not see the correct process state if it accessed the + the ``state`` attribute on a ``Subprocess`` instance immediately in the + the event callback. Patch by Chao Wang. + - Added the ``setuptools`` package to the list of dependencies in ``setup.py`` because it is a runtime dependency. Patch by Louis Sautier. diff --git a/supervisor/process.py b/supervisor/process.py index 075204a06..be7e81c27 100644 --- a/supervisor/process.py +++ b/supervisor/process.py @@ -165,18 +165,17 @@ def change_state(self, new_state, expected=True): # exists for unit tests return False - event_class = self.event_map.get(new_state) self.state = new_state - - if event_class is not None: - event = event_class(self, old_state, expected) - events.notify(event) - if new_state == ProcessStates.BACKOFF: now = time.time() self.backoff += 1 self.delay = now + self.backoff + event_class = self.event_map.get(new_state) + if event_class is not None: + event = event_class(self, old_state, expected) + events.notify(event) + def _assertInState(self, *states): if self.state not in states: current_state = getProcessStateDescription(self.state) diff --git a/supervisor/tests/test_process.py b/supervisor/tests/test_process.py index bc9ade41c..6977225fb 100644 --- a/supervisor/tests/test_process.py +++ b/supervisor/tests/test_process.py @@ -1322,8 +1322,8 @@ def test_cmp_bypriority(self): def test_transition_stopped_to_starting_supervisor_stopping(self): from supervisor import events - L = [] - events.subscribe(events.ProcessStateEvent, lambda x: L.append(x)) + emitted_events = [] + events.subscribe(events.ProcessStateEvent, emitted_events.append) from supervisor.states import ProcessStates, SupervisorStates options = DummyOptions() options.mood = SupervisorStates.SHUTDOWN @@ -1335,12 +1335,14 @@ def test_transition_stopped_to_starting_supervisor_stopping(self): process.state = ProcessStates.STOPPED process.transition() self.assertEqual(process.state, ProcessStates.STOPPED) - self.assertEqual(L, []) + self.assertEqual(emitted_events, []) def test_transition_stopped_to_starting_supervisor_running(self): from supervisor import events - L = [] - events.subscribe(events.ProcessStateEvent, lambda x: L.append(x)) + emitted_events_with_states = [] + def subscriber(e): + emitted_events_with_states.append((e, e.process.state)) + events.subscribe(events.ProcessStateEvent, subscriber) from supervisor.states import ProcessStates, SupervisorStates options = DummyOptions() options.mood = SupervisorStates.RUNNING @@ -1351,14 +1353,16 @@ def test_transition_stopped_to_starting_supervisor_running(self): process.state = ProcessStates.STOPPED process.transition() self.assertEqual(process.state, ProcessStates.STARTING) - self.assertEqual(len(L), 1) - event = L[0] + self.assertEqual(len(emitted_events_with_states), 1) + event, state_when_event_emitted = emitted_events_with_states[0] self.assertEqual(event.__class__, events.ProcessStateStartingEvent) + self.assertEqual(event.from_state, ProcessStates.STOPPED) + self.assertEqual(state_when_event_emitted, ProcessStates.STARTING) def test_transition_exited_to_starting_supervisor_stopping(self): from supervisor import events - L = [] - events.subscribe(events.ProcessStateEvent, lambda x: L.append(x)) + emitted_events = [] + events.subscribe(events.ProcessStateEvent, emitted_events.append) from supervisor.states import ProcessStates, SupervisorStates options = DummyOptions() options.mood = SupervisorStates.SHUTDOWN @@ -1374,12 +1378,14 @@ def test_transition_exited_to_starting_supervisor_stopping(self): process.transition() self.assertEqual(process.state, ProcessStates.EXITED) self.assertTrue(process.system_stop) - self.assertEqual(L, []) + self.assertEqual(emitted_events, []) def test_transition_exited_to_starting_uncond_supervisor_running(self): from supervisor import events - L = [] - events.subscribe(events.ProcessStateEvent, lambda x: L.append(x)) + emitted_events_with_states = [] + def subscriber(e): + emitted_events_with_states.append((e, e.process.state)) + events.subscribe(events.ProcessStateEvent, subscriber) from supervisor.states import ProcessStates options = DummyOptions() @@ -1391,14 +1397,18 @@ def test_transition_exited_to_starting_uncond_supervisor_running(self): process.state = ProcessStates.EXITED process.transition() self.assertEqual(process.state, ProcessStates.STARTING) - self.assertEqual(len(L), 1) - event = L[0] + self.assertEqual(len(emitted_events_with_states), 1) + event, state_when_event_emitted = emitted_events_with_states[0] self.assertEqual(event.__class__, events.ProcessStateStartingEvent) + self.assertEqual(event.from_state, ProcessStates.EXITED) + self.assertEqual(state_when_event_emitted, ProcessStates.STARTING) def test_transition_exited_to_starting_condit_supervisor_running(self): from supervisor import events - L = [] - events.subscribe(events.ProcessStateEvent, lambda x: L.append(x)) + emitted_events_with_states = [] + def subscriber(e): + emitted_events_with_states.append((e, e.process.state)) + events.subscribe(events.ProcessStateEvent, subscriber) from supervisor.states import ProcessStates options = DummyOptions() @@ -1411,14 +1421,16 @@ def test_transition_exited_to_starting_condit_supervisor_running(self): process.exitstatus = 'bogus' process.transition() self.assertEqual(process.state, ProcessStates.STARTING) - self.assertEqual(len(L), 1) - event = L[0] + self.assertEqual(len(emitted_events_with_states), 1) + event, state_when_event_emitted = emitted_events_with_states[0] self.assertEqual(event.__class__, events.ProcessStateStartingEvent) + self.assertEqual(event.from_state, ProcessStates.EXITED) + self.assertEqual(state_when_event_emitted, ProcessStates.STARTING) def test_transition_exited_to_starting_condit_fls_supervisor_running(self): from supervisor import events - L = [] - events.subscribe(events.ProcessStateEvent, lambda x: L.append(x)) + emitted_events = [] + events.subscribe(events.ProcessStateEvent, emitted_events.append) from supervisor.states import ProcessStates options = DummyOptions() @@ -1431,11 +1443,11 @@ def test_transition_exited_to_starting_condit_fls_supervisor_running(self): process.exitstatus = 0 process.transition() self.assertEqual(process.state, ProcessStates.EXITED) - self.assertEqual(L, []) + self.assertEqual(emitted_events, []) def test_transition_backoff_to_starting_supervisor_stopping(self): from supervisor import events - L = [] + emitted_events = [] events.subscribe(events.ProcessStateEvent, lambda x: L.append(x)) from supervisor.states import ProcessStates, SupervisorStates options = DummyOptions() @@ -1449,12 +1461,14 @@ def test_transition_backoff_to_starting_supervisor_stopping(self): process.state = ProcessStates.BACKOFF process.transition() self.assertEqual(process.state, ProcessStates.BACKOFF) - self.assertEqual(L, []) + self.assertEqual(emitted_events, []) def test_transition_backoff_to_starting_supervisor_running(self): from supervisor import events - L = [] - events.subscribe(events.ProcessStateEvent, lambda x: L.append(x)) + emitted_events_with_states = [] + def subscriber(e): + emitted_events_with_states.append((e, e.process.state)) + events.subscribe(events.ProcessStateEvent, subscriber) from supervisor.states import ProcessStates, SupervisorStates options = DummyOptions() options.mood = SupervisorStates.RUNNING @@ -1467,12 +1481,15 @@ def test_transition_backoff_to_starting_supervisor_running(self): process.state = ProcessStates.BACKOFF process.transition() self.assertEqual(process.state, ProcessStates.STARTING) - self.assertEqual(len(L), 1) - self.assertEqual(L[0].__class__, events.ProcessStateStartingEvent) + self.assertEqual(len(emitted_events_with_states), 1) + event, state_when_event_emitted = emitted_events_with_states[0] + self.assertEqual(event.__class__, events.ProcessStateStartingEvent) + self.assertEqual(event.from_state, ProcessStates.BACKOFF) + self.assertEqual(state_when_event_emitted, ProcessStates.STARTING) def test_transition_backoff_to_starting_supervisor_running_notyet(self): from supervisor import events - L = [] + emitted_events = [] events.subscribe(events.ProcessStateEvent, lambda x: L.append(x)) from supervisor.states import ProcessStates, SupervisorStates options = DummyOptions() @@ -1486,12 +1503,14 @@ def test_transition_backoff_to_starting_supervisor_running_notyet(self): process.state = ProcessStates.BACKOFF process.transition() self.assertEqual(process.state, ProcessStates.BACKOFF) - self.assertEqual(L, []) + self.assertEqual(emitted_events, []) def test_transition_starting_to_running(self): from supervisor import events - L = [] - events.subscribe(events.ProcessStateEvent, lambda x: L.append(x)) + emitted_events_with_states = [] + def subscriber(e): + emitted_events_with_states.append((e, e.process.state)) + events.subscribe(events.ProcessStateEvent, subscriber) from supervisor.states import ProcessStates options = DummyOptions() @@ -1516,14 +1535,18 @@ def test_transition_starting_to_running(self): self.assertEqual(options.logger.data[0], 'success: process entered RUNNING state, process has ' 'stayed up for > than 10 seconds (startsecs)') - self.assertEqual(len(L), 1) - event = L[0] + self.assertEqual(len(emitted_events_with_states), 1) + event, state_when_event_emitted = emitted_events_with_states[0] self.assertEqual(event.__class__, events.ProcessStateRunningEvent) + self.assertEqual(event.from_state, ProcessStates.STARTING) + self.assertEqual(state_when_event_emitted, ProcessStates.RUNNING) def test_transition_starting_to_running_laststart_in_future(self): from supervisor import events - L = [] - events.subscribe(events.ProcessStateEvent, lambda x: L.append(x)) + emitted_events_with_states = [] + def subscriber(e): + emitted_events_with_states.append((e, e.process.state)) + events.subscribe(events.ProcessStateEvent, subscriber) from supervisor.states import ProcessStates future_time = time.time() + 3600 # 1 hour into the future @@ -1568,14 +1591,18 @@ def test_transition_starting_to_running_laststart_in_future(self): self.assertEqual(options.logger.data[0], 'success: process entered RUNNING state, process has ' 'stayed up for > than {} seconds (startsecs)'.format(test_startsecs)) - self.assertEqual(len(L), 1) - event = L[0] + self.assertEqual(len(emitted_events_with_states), 1) + event, state_when_event_emitted = emitted_events_with_states[0] self.assertEqual(event.__class__, events.ProcessStateRunningEvent) + self.assertEqual(event.from_state, ProcessStates.STARTING) + self.assertEqual(state_when_event_emitted, ProcessStates.RUNNING) def test_transition_backoff_to_starting_delay_in_future(self): from supervisor import events - L = [] - events.subscribe(events.ProcessStateEvent, lambda x: L.append(x)) + emitted_events_with_states = [] + def subscriber(e): + emitted_events_with_states.append((e, e.process.state)) + events.subscribe(events.ProcessStateEvent, subscriber) from supervisor.states import ProcessStates future_time = time.time() + 3600 # 1 hour into the future @@ -1603,13 +1630,18 @@ def test_transition_backoff_to_starting_delay_in_future(self): process.transition() self.assertEqual(process.state, ProcessStates.STARTING) - self.assertEqual(len(L), 1) - self.assertEqual(L[0].__class__, events.ProcessStateStartingEvent) + self.assertEqual(len(emitted_events_with_states), 1) + event, state_when_event_emitted = emitted_events_with_states[0] + self.assertEqual(event.__class__, events.ProcessStateStartingEvent) + self.assertEqual(event.from_state, ProcessStates.BACKOFF) + self.assertEqual(state_when_event_emitted, ProcessStates.STARTING) def test_transition_backoff_to_fatal(self): from supervisor import events - L = [] - events.subscribe(events.ProcessStateEvent, lambda x: L.append(x)) + emitted_events_with_states = [] + def subscriber(e): + emitted_events_with_states.append((e, e.process.state)) + events.subscribe(events.ProcessStateEvent, subscriber) from supervisor.states import ProcessStates options = DummyOptions() @@ -1633,14 +1665,16 @@ def test_transition_backoff_to_fatal(self): self.assertEqual(options.logger.data[0], 'gave up: process entered FATAL state, too many start' ' retries too quickly') - self.assertEqual(len(L), 1) - event = L[0] + self.assertEqual(len(emitted_events_with_states), 1) + event, state_when_event_emitted = emitted_events_with_states[0] self.assertEqual(event.__class__, events.ProcessStateFatalEvent) + self.assertEqual(event.from_state, ProcessStates.BACKOFF) + self.assertEqual(state_when_event_emitted, ProcessStates.FATAL) def test_transition_stops_unkillable_notyet(self): from supervisor import events - L = [] - events.subscribe(events.ProcessStateEvent, lambda x: L.append(x)) + emitted_events = [] + events.subscribe(events.ProcessStateEvent, emitted_events.append) from supervisor.states import ProcessStates options = DummyOptions() @@ -1651,12 +1685,12 @@ def test_transition_stops_unkillable_notyet(self): process.transition() self.assertEqual(process.state, ProcessStates.STOPPING) - self.assertEqual(L, []) + self.assertEqual(emitted_events, []) def test_transition_stops_unkillable(self): from supervisor import events - L = [] - events.subscribe(events.ProcessStateEvent, lambda x: L.append(x)) + emitted_events = [] + events.subscribe(events.ProcessStateEvent, emitted_events.append) from supervisor.states import ProcessStates options = DummyOptions() @@ -1674,7 +1708,7 @@ def test_transition_stops_unkillable(self): self.assertEqual(options.logger.data[0], "killing 'process' (1) with SIGKILL") self.assertEqual(options.kills[1], signal.SIGKILL) - self.assertEqual(L, []) + self.assertEqual(emitted_events, []) def test_change_state_doesnt_notify_if_no_state_change(self): options = DummyOptions() From 762f036943d58beec11c24db073e428c8824afd1 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Tue, 21 Dec 2021 14:31:04 -0800 Subject: [PATCH 026/129] Run tests on GitHub Actions --- .github/workflows/main.yml | 75 ++++++++++++++++++++++++++++++++++++++ .travis.yml | 57 ----------------------------- tox.ini | 8 ++-- 3 files changed, 79 insertions(+), 61 deletions(-) create mode 100644 .github/workflows/main.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 000000000..b38aaf16a --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,75 @@ +name: Run all tests + +on: [push] + +jobs: + tests: + runs-on: ubuntu-18.04 + strategy: + fail-fast: false + matrix: + python-version: [2.7, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, "3.10"] + + steps: + - uses: actions/checkout@v2 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Show Python version + run: python -V + + - name: Set TOXENV based on Python version + run: python -c 'import sys; print("TOXENV=py%d%d" % (sys.version_info.major, sys.version_info.minor))' | tee -a $GITHUB_ENV + + - name: Install dependencies + run: pip install virtualenv tox + + - name: Run the unit tests + run: tox + + - name: Run the end-to-end tests + run: END_TO_END=1 tox + + coverage: + runs-on: ubuntu-18.04 + strategy: + fail-fast: false + matrix: + python-version: [2.7, 3.7] + + steps: + - uses: actions/checkout@v2 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: pip install virtualenv tox + + - name: Set TOXENV based on Python version + run: python -c 'import sys; e="cover" if sys.version_info.major == 2 else "cover3"; print("TOXENV=%s" % e)' | tee -a $GITHUB_ENV + + - name: Run unit test coverage + run: tox + + docs: + runs-on: ubuntu-18.04 + + steps: + - uses: actions/checkout@v2 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: "3.8" + + - name: Install dependencies + run: pip install virtualenv tox + + - name: Build the docs + run: TOXENV=docs tox diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 8e7548606..000000000 --- a/.travis.yml +++ /dev/null @@ -1,57 +0,0 @@ -language: python - -matrix: - include: - - python: 2.7 - env: TOXENV=cover - - python: 3.7 - env: TOXENV=cover3 - dist: xenial # required for Python >= 3.7 - - python: 3.7 - env: TOXENV=docs - dist: xenial # required for Python >= 3.7 - - python: 2.7 - env: - - TOXENV=py27 - - END_TO_END=1 - - python: 2.7 - env: - - TOXENV=py27-configparser - - END_TO_END=1 - - python: 3.4 - env: - - TOXENV=py34 - - END_TO_END=1 - - python: 3.5 - env: - - TOXENV=py35 - - END_TO_END=1 - - python: 3.6 - env: - - TOXENV=py36 - - END_TO_END=1 - - python: 3.7 - env: - - TOXENV=py37 - - END_TO_END=1 - dist: xenial # required for Python >= 3.7 - - - python: 3.8 - env: - - TOXENV=py38 - - END_TO_END=1 - dist: xenial # required for Python >= 3.7 - - - name: "Python: 3.7 on macOS" - language: sh # 'language: python' is not yet supported on Travis CI macOS - os: osx - osx_image: xcode10.2 # Python 3.7.2 running on macOS 10.14.3 - env: - - TOXENV=py37 - - END_TO_END=1 - -install: - - travis_retry pip install virtualenv tox - -script: - - travis_retry tox diff --git a/tox.ini b/tox.ini index 74dad21dd..28953354b 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] envlist = - cover,cover3,docs,py27,py34,py35,py36,py37,py38 + cover,cover3,docs,py27,py34,py35,py36,py37,py38,py39,py310 [testenv] deps = @@ -10,7 +10,7 @@ deps = mock >= 0.5.0 passenv = END_TO_END commands = - py.test {posargs} + pytest {posargs} [testenv:py27-configparser] ;see https://github.com/Supervisor/supervisor/issues/1230 @@ -24,7 +24,7 @@ commands = {[testenv]commands} [testenv:cover] basepython = python2.7 commands = - py.test --cov=supervisor --cov-report=term-missing --cov-report=xml {posargs} + pytest --cov=supervisor --cov-report=term-missing --cov-report=xml {posargs} deps = {[testenv]deps} pytest-cov @@ -32,7 +32,7 @@ deps = [testenv:cover3] basepython = python3.7 commands = - py.test --cov=supervisor --cov-report=term-missing --cov-report=xml {posargs} + pytest --cov=supervisor --cov-report=term-missing --cov-report=xml {posargs} deps = {[testenv:cover]deps} From 285f4d4e3d6344b0ad0a7f66dedbdd22d5e2454f Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Mon, 27 Dec 2021 11:56:20 -0800 Subject: [PATCH 027/129] Prepare 4.2.3 release --- CHANGES.rst | 4 ++-- supervisor/version.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 388e4a64a..6c21ed9e7 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,5 +1,5 @@ -4.3.0.dev0 (Next Release) -------------------------- +4.2.3 (2021-12-27) +------------------ - Fixed a race condition where an ``rpcinterface`` extension that subscribed to events would not see the correct process state if it accessed the diff --git a/supervisor/version.txt b/supervisor/version.txt index b75a1c1c0..f2c6cb6af 100644 --- a/supervisor/version.txt +++ b/supervisor/version.txt @@ -1 +1 @@ -4.3.0.dev0 +4.2.3 From 863a8568e14a34e05edf74f44b730cd1e0785f3c Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Mon, 27 Dec 2021 12:01:24 -0800 Subject: [PATCH 028/129] Fix typo --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 6c21ed9e7..70a0216a7 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,7 +4,7 @@ - Fixed a race condition where an ``rpcinterface`` extension that subscribed to events would not see the correct process state if it accessed the the ``state`` attribute on a ``Subprocess`` instance immediately in the - the event callback. Patch by Chao Wang. + event callback. Patch by Chao Wang. - Added the ``setuptools`` package to the list of dependencies in ``setup.py`` because it is a runtime dependency. Patch by Louis Sautier. From 601c102b487437904af9d2cec2bcf84644573bac Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Mon, 27 Dec 2021 12:06:43 -0800 Subject: [PATCH 029/129] Back to .dev0 until next release --- CHANGES.rst | 3 +++ supervisor/version.txt | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 70a0216a7..e5ae943a8 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,6 @@ +4.3.0.dev0 (Next Release) +------------------------- + 4.2.3 (2021-12-27) ------------------ diff --git a/supervisor/version.txt b/supervisor/version.txt index f2c6cb6af..b75a1c1c0 100644 --- a/supervisor/version.txt +++ b/supervisor/version.txt @@ -1 +1 @@ -4.2.3 +4.3.0.dev0 From 88037ca3e3ee5b6e3a3119340a1f0d389f61608e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Le=20Cl=C3=A9ach?= Date: Tue, 28 Dec 2021 12:53:21 +0100 Subject: [PATCH 030/129] identifier set in command line overwritten by default value the identifier may have been set in the command line and not in the configuration file. the last line of ServerOptions.realize systematically overwrites the self.identifier by the value from the configuration file (may be the default one). if the identifier was set in the configuration file, the self.identifier was already updated. so there's no point keeping this instruction. --- supervisor/options.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/supervisor/options.py b/supervisor/options.py index 7b53cc760..255c5bfbd 100644 --- a/supervisor/options.py +++ b/supervisor/options.py @@ -543,8 +543,6 @@ def realize(self, *arg, **kw): # self.serverurl may still be None if no servers at all are # configured in the config file - self.identifier = section.identifier - def process_config(self, do_usage=True): Options.process_config(self, do_usage=do_usage) From 68c2a36d1e04dc0bad0f38038302ca1529206882 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Tue, 28 Dec 2021 11:58:19 -0800 Subject: [PATCH 031/129] Add failing tests for #1483 --- supervisor/tests/fixtures/issue-1483a.conf | 11 +++++ supervisor/tests/fixtures/issue-1483b.conf | 12 +++++ supervisor/tests/fixtures/issue-1483c.conf | 12 +++++ supervisor/tests/test_end_to_end.py | 56 ++++++++++++++++++++++ supervisor/tests/test_options.py | 10 ++++ 5 files changed, 101 insertions(+) create mode 100644 supervisor/tests/fixtures/issue-1483a.conf create mode 100644 supervisor/tests/fixtures/issue-1483b.conf create mode 100644 supervisor/tests/fixtures/issue-1483c.conf diff --git a/supervisor/tests/fixtures/issue-1483a.conf b/supervisor/tests/fixtures/issue-1483a.conf new file mode 100644 index 000000000..1fe93ade4 --- /dev/null +++ b/supervisor/tests/fixtures/issue-1483a.conf @@ -0,0 +1,11 @@ +[supervisord] +loglevel=info ; log level; default info; others: debug,warn,trace +logfile=/tmp/issue-1483a.log ; main log file; default $CWD/supervisord.log +pidfile=/tmp/issue-1483a.pid ; supervisord pidfile; default supervisord.pid +nodaemon=true ; start in foreground if true; default false + +[rpcinterface:supervisor] +supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface + +[unix_http_server] +file=/tmp/issue-1483a.sock ; the path to the socket file diff --git a/supervisor/tests/fixtures/issue-1483b.conf b/supervisor/tests/fixtures/issue-1483b.conf new file mode 100644 index 000000000..cac9ccd18 --- /dev/null +++ b/supervisor/tests/fixtures/issue-1483b.conf @@ -0,0 +1,12 @@ +[supervisord] +loglevel=info ; log level; default info; others: debug,warn,trace +logfile=/tmp/issue-1483b.log ; main log file; default $CWD/supervisord.log +pidfile=/tmp/issue-1483b.pid ; supervisord pidfile; default supervisord.pid +nodaemon=true ; start in foreground if true; default false +identifier=from_config_file + +[rpcinterface:supervisor] +supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface + +[unix_http_server] +file=/tmp/issue-1483b.sock ; the path to the socket file diff --git a/supervisor/tests/fixtures/issue-1483c.conf b/supervisor/tests/fixtures/issue-1483c.conf new file mode 100644 index 000000000..e7ffd7e03 --- /dev/null +++ b/supervisor/tests/fixtures/issue-1483c.conf @@ -0,0 +1,12 @@ +[supervisord] +loglevel=info ; log level; default info; others: debug,warn,trace +logfile=/tmp/issue-1483c.log ; main log file; default $CWD/supervisord.log +pidfile=/tmp/issue-1483c.pid ; supervisord pidfile; default supervisord.pid +nodaemon=true ; start in foreground if true; default false +identifier=from_config_file + +[rpcinterface:supervisor] +supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface + +[unix_http_server] +file=/tmp/issue-1483c.sock ; the path to the socket file diff --git a/supervisor/tests/test_end_to_end.py b/supervisor/tests/test_end_to_end.py index 0a9b37026..dd5c977f1 100644 --- a/supervisor/tests/test_end_to_end.py +++ b/supervisor/tests/test_end_to_end.py @@ -363,6 +363,62 @@ def test_issue_1418_pidproxy_cmd_with_args(self): pidproxy.expect(pexpect.EOF) self.assertEqual(pidproxy.before.strip(), "1 2") + def test_issue_1483a_identifier_default(self): + """When no identifier is supplied on the command line or in the config + file, the default is used.""" + filename = pkg_resources.resource_filename(__name__, 'fixtures/issue-1483a.conf') + args = ['-m', 'supervisor.supervisord', '-c', filename] + supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') + self.addCleanup(supervisord.kill, signal.SIGINT) + supervisord.expect_exact('supervisord started with pid') + + from supervisor.compat import xmlrpclib + from supervisor.xmlrpc import SupervisorTransport + transport = SupervisorTransport('', '', 'unix:///tmp/issue-1483a.sock') + try: + server = xmlrpclib.ServerProxy('http://transport.ignores.host/RPC2', transport) + ident = server.supervisor.getIdentification() + finally: + transport.close() + self.assertEqual(ident, "supervisor") + + def test_issue_1483b_identifier_from_config_file(self): + """When the identifier is supplied in the config file only, that + identifier is used instead of the default.""" + filename = pkg_resources.resource_filename(__name__, 'fixtures/issue-1483b.conf') + args = ['-m', 'supervisor.supervisord', '-c', filename] + supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') + self.addCleanup(supervisord.kill, signal.SIGINT) + supervisord.expect_exact('supervisord started with pid') + + from supervisor.compat import xmlrpclib + from supervisor.xmlrpc import SupervisorTransport + transport = SupervisorTransport('', '', 'unix:///tmp/issue-1483b.sock') + try: + server = xmlrpclib.ServerProxy('http://transport.ignores.host/RPC2', transport) + ident = server.supervisor.getIdentification() + finally: + transport.close() + self.assertEqual(ident, "from_config_file") + + def test_issue_1483c_identifier_from_command_line(self): + """When an identifier is supplied in both the config file and on the + command line, the one from the command line is used.""" + filename = pkg_resources.resource_filename(__name__, 'fixtures/issue-1483c.conf') + args = ['-m', 'supervisor.supervisord', '-c', filename, '-i', 'from_command_line'] + supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') + self.addCleanup(supervisord.kill, signal.SIGINT) + supervisord.expect_exact('supervisord started with pid') + + from supervisor.compat import xmlrpclib + from supervisor.xmlrpc import SupervisorTransport + transport = SupervisorTransport('', '', 'unix:///tmp/issue-1483c.sock') + try: + server = xmlrpclib.ServerProxy('http://transport.ignores.host/RPC2', transport) + ident = server.supervisor.getIdentification() + finally: + transport.close() + self.assertEqual(ident, "from_command_line") def test_suite(): return unittest.findTestCases(sys.modules[__name__]) diff --git a/supervisor/tests/test_options.py b/supervisor/tests/test_options.py index a80deb08f..7a8c78ca7 100644 --- a/supervisor/tests/test_options.py +++ b/supervisor/tests/test_options.py @@ -1153,6 +1153,16 @@ def record_usage(message): self.assertEqual(len(recorder), 1) self.assertEqual(recorder[0], "option --bad not recognized") + def test_realize_prefers_identifier_from_args(self): + text = lstrip(""" + [supervisord] + identifier=from_config_file + """) + instance = self._makeOne() + instance.configfile = StringIO(text) + instance.realize(args=['-i', 'from_args']) + self.assertEqual(instance.identifier, "from_args") + def test_options_afunix(self): instance = self._makeOne() text = lstrip("""\ From db27701ad75b7f23328fc0ae13d216328bdff2b8 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Tue, 28 Dec 2021 13:10:08 -0800 Subject: [PATCH 032/129] Add changelog entry for 88037ca3e3ee5b6e3a3119340a1f0d389f61608e --- CHANGES.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index e5ae943a8..f99974a84 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,10 @@ 4.3.0.dev0 (Next Release) ------------------------- +- Fixed a bug where the ``--identifier`` command line argument was ignored. + It was broken since at least 3.0a7 (released in 2009) and probably earlier. + Patch by Julien Le Cléach. + 4.2.3 (2021-12-27) ------------------ From b6052f4b7b9f74376c7a70673921806688ebf05b Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Tue, 28 Dec 2021 14:15:46 -0800 Subject: [PATCH 033/129] Run tests on pull requests --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b38aaf16a..6ba848603 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,6 +1,6 @@ name: Run all tests -on: [push] +on: [push, pull_request] jobs: tests: From 42d862865259b58b6854ede78dab36ddec5a4499 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Tue, 28 Dec 2021 14:29:25 -0800 Subject: [PATCH 034/129] Fix pyflakes warnings --- supervisor/tests/base.py | 3 --- supervisor/tests/test_process.py | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/supervisor/tests/base.py b/supervisor/tests/base.py index bef82e964..7553a2cce 100644 --- a/supervisor/tests/base.py +++ b/supervisor/tests/base.py @@ -1,9 +1,7 @@ _NOW = 1151365354 _TIMEFORMAT = '%b %d %I:%M %p' -import errno import functools -import os from supervisor.compat import Fault from supervisor.compat import as_bytes @@ -235,7 +233,6 @@ def mktempfile(self, prefix, suffix, dir): return self.tempfile_name def remove(self, path): - import os if self.remove_exception is not None: raise self.remove_exception self.removed.append(path) diff --git a/supervisor/tests/test_process.py b/supervisor/tests/test_process.py index 6977225fb..edd02b850 100644 --- a/supervisor/tests/test_process.py +++ b/supervisor/tests/test_process.py @@ -1448,7 +1448,7 @@ def test_transition_exited_to_starting_condit_fls_supervisor_running(self): def test_transition_backoff_to_starting_supervisor_stopping(self): from supervisor import events emitted_events = [] - events.subscribe(events.ProcessStateEvent, lambda x: L.append(x)) + events.subscribe(events.ProcessStateEvent, lambda x: emitted_events.append(x)) from supervisor.states import ProcessStates, SupervisorStates options = DummyOptions() options.mood = SupervisorStates.SHUTDOWN @@ -1490,7 +1490,7 @@ def subscriber(e): def test_transition_backoff_to_starting_supervisor_running_notyet(self): from supervisor import events emitted_events = [] - events.subscribe(events.ProcessStateEvent, lambda x: L.append(x)) + events.subscribe(events.ProcessStateEvent, lambda x: emitted_events.append(x)) from supervisor.states import ProcessStates, SupervisorStates options = DummyOptions() options.mood = SupervisorStates.RUNNING From 602d6f574a3cba021ec3865bee6ebe20149fff26 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Thu, 30 Dec 2021 12:39:59 -0800 Subject: [PATCH 035/129] Prepare 4.2.4 release --- CHANGES.rst | 4 ++-- supervisor/version.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index f99974a84..bbf785d5d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,5 +1,5 @@ -4.3.0.dev0 (Next Release) -------------------------- +4.2.4 (2021-12-30) +------------------ - Fixed a bug where the ``--identifier`` command line argument was ignored. It was broken since at least 3.0a7 (released in 2009) and probably earlier. diff --git a/supervisor/version.txt b/supervisor/version.txt index b75a1c1c0..cf78d5b6a 100644 --- a/supervisor/version.txt +++ b/supervisor/version.txt @@ -1 +1 @@ -4.3.0.dev0 +4.2.4 From 6283ba46f2db7420b8afd28863dcea6ea3909a02 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Thu, 30 Dec 2021 12:47:21 -0800 Subject: [PATCH 036/129] Back to .dev0 until next release --- CHANGES.rst | 4 ++++ supervisor/version.txt | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index bbf785d5d..2d2a84713 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,7 @@ +4.3.0.dev0 (Next Release) +------------------------- + + 4.2.4 (2021-12-30) ------------------ diff --git a/supervisor/version.txt b/supervisor/version.txt index cf78d5b6a..b75a1c1c0 100644 --- a/supervisor/version.txt +++ b/supervisor/version.txt @@ -1 +1 @@ -4.2.4 +4.3.0.dev0 From 28621d46c81dbf6a8b58548a944801fbdc257ff2 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Fri, 31 Dec 2021 13:56:20 -0800 Subject: [PATCH 037/129] Add changelog entry for 6a5efbe77dc5372115064c0ce76c423e3a3e692c --- CHANGES.rst | 4 ++++ docs/configuration.rst | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 2d2a84713..aa3a33e05 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,10 @@ 4.3.0.dev0 (Next Release) ------------------------- +- ``supervisorctl`` now reads extra files included via the ``[include]`` + section in ``supervisord.conf`` like ``supervisord`` does. This allows + the ``[supervisorctl]`` section or ``[ctlplugin:x]`` sections to be in + included files. Patch by François Granade. 4.2.4 (2021-12-30) ------------------ diff --git a/docs/configuration.rst b/docs/configuration.rst index 3c36f6863..26b7c8b76 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -1208,6 +1208,10 @@ configuration. *Changed*: 3.3.0. Added support for the ``host_node_name`` expansion. + *Changed*: 4.3.0. Added support to :program:`supervisorctl` for reading + files specified in the ``[include]`` section. In previous versions, + the ``[include]`` section was only supported by :program:`supervisord`. + ``[include]`` Section Example ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From ef821456a8758e0f156df2caf8660f993ae3f01d Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Sat, 1 Jan 2022 11:32:54 -0800 Subject: [PATCH 038/129] Remove Python 2.6 compatibility code Unit tests for unix:// already exist and still pass --- supervisor/datatypes.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/supervisor/datatypes.py b/supervisor/datatypes.py index 5a8994c48..24932d95e 100644 --- a/supervisor/datatypes.py +++ b/supervisor/datatypes.py @@ -391,10 +391,7 @@ def __call__(self, v): 'gb': 1024*1024*long(1024),}) def url(value): - # earlier Python 2.6 urlparse (2.6.4 and under) can't parse unix:// URLs, - # later ones can but we need to straddle - uri = value.replace('unix://', 'http://', 1).strip() - scheme, netloc, path, params, query, fragment = urlparse.urlparse(uri) + scheme, netloc, path, params, query, fragment = urlparse.urlparse(value) if scheme and (netloc or path): return value raise ValueError("value %r is not a URL" % value) From 7de6215c9677d1418e7f0a72e7065c1fa69174d4 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Sat, 1 Jan 2022 12:16:40 -0800 Subject: [PATCH 039/129] Remove unused variable Introduced in 6a5efbe77dc5372115064c0ce76c423e3a3e692c but not used anywhere --- supervisor/options.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/supervisor/options.py b/supervisor/options.py index 83602545e..e3d7e7a67 100644 --- a/supervisor/options.py +++ b/supervisor/options.py @@ -1683,8 +1683,6 @@ def realize(self, *arg, **kw): if not self.args: self.interactive = 1 - self.exit_on_error = 0 if self.interactive else 1 - format = '%(levelname)s: %(message)s\n' logger = loggers.getLogger() loggers.handle_stdout(logger, format) From 76779991b3cb949b9cd8ba233ebb8e254ebcac1d Mon Sep 17 00:00:00 2001 From: alexandertuna Date: Thu, 20 Jan 2022 16:30:23 +0100 Subject: [PATCH 040/129] Tell user which default config file was chosen --- supervisor/options.py | 1 + 1 file changed, 1 insertion(+) diff --git a/supervisor/options.py b/supervisor/options.py index e3d7e7a67..20c1b07aa 100644 --- a/supervisor/options.py +++ b/supervisor/options.py @@ -128,6 +128,7 @@ def default_configfile(self): for path in self.searchpaths: if os.path.exists(path): config = path + self.stdout.write("Chose default config file: %s\n" % config) break if config is None and self.require_configfile: self.usage('No config file found at default paths (%s); ' From 4235291c2d5ef1f1933c81e29f365133fa331f45 Mon Sep 17 00:00:00 2001 From: Julien LE CLEACH Date: Sat, 12 Mar 2022 11:38:18 +0100 Subject: [PATCH 041/129] fix unmanaged BadCommand exception in startProcess --- supervisor/rpcinterface.py | 3 ++- supervisor/tests/test_rpcinterfaces.py | 12 ++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/supervisor/rpcinterface.py b/supervisor/rpcinterface.py index 854b7285f..5188e9e06 100644 --- a/supervisor/rpcinterface.py +++ b/supervisor/rpcinterface.py @@ -15,6 +15,7 @@ from supervisor.options import readFile from supervisor.options import tailFile +from supervisor.options import BadCommand from supervisor.options import NotExecutable from supervisor.options import NotFound from supervisor.options import NoPermission @@ -293,7 +294,7 @@ def startProcess(self, name, wait=True): filename, argv = process.get_execv_args() except NotFound as why: raise RPCError(Faults.NO_FILE, why.args[0]) - except (NotExecutable, NoPermission) as why: + except (BadCommand, NotExecutable, NoPermission) as why: raise RPCError(Faults.NOT_EXECUTABLE, why.args[0]) if process.get_state() in RUNNING_STATES: diff --git a/supervisor/tests/test_rpcinterfaces.py b/supervisor/tests/test_rpcinterfaces.py index a6a319871..0827adf05 100644 --- a/supervisor/tests/test_rpcinterfaces.py +++ b/supervisor/tests/test_rpcinterfaces.py @@ -369,6 +369,18 @@ def test_startProcess_file_not_found(self): self._assertRPCError(xmlrpc.Faults.NO_FILE, interface.startProcess, 'foo') + def test_startProcess_bad_command(self): + options = DummyOptions() + pconfig = DummyPConfig(options, 'foo', '/foo/bar', autostart=False) + from supervisor.options import BadCommand + supervisord = PopulatedDummySupervisor(options, 'foo', pconfig) + process = supervisord.process_groups['foo'].processes['foo'] + process.execv_arg_exception = BadCommand + interface = self._makeOne(supervisord) + from supervisor import xmlrpc + self._assertRPCError(xmlrpc.Faults.NOT_EXECUTABLE, + interface.startProcess, 'foo') + def test_startProcess_file_not_executable(self): options = DummyOptions() pconfig = DummyPConfig(options, 'foo', '/foo/bar', autostart=False) From 44b288785f8905a414dac04e1488f03e1217ecba Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Sun, 27 Mar 2022 14:45:13 -0700 Subject: [PATCH 042/129] Add changelog entry for 76779991b3cb949b9cd8ba233ebb8e254ebcac1d --- CHANGES.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index aa3a33e05..a9b013745 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -6,6 +6,10 @@ the ``[supervisorctl]`` section or ``[ctlplugin:x]`` sections to be in included files. Patch by François Granade. +- If ``supervisord`` searches the default paths for its config file (no + ``-c`` flag given), it will now print a message showing the path of the + config file that it loaded. Patch by Alexander Tuna. + 4.2.4 (2021-12-30) ------------------ From 5ced742bba37c61ef027f88b4b2b758ba6e975eb Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Sun, 27 Mar 2022 14:49:52 -0700 Subject: [PATCH 043/129] Add changelog entry for 9ccf0fbe33379e940df286f8984e7dd461316c03 --- CHANGES.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index a9b013745..987a241f2 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -10,6 +10,10 @@ ``-c`` flag given), it will now print a message showing the path of the config file that it loaded. Patch by Alexander Tuna. +- The return value of the XML-RPC method ``supervisor.getAllConfigInfo()`` + now includes the ``directory``, ``uid``, and ``serverurl`` of the + program. Patch by Yellmean. + 4.2.4 (2021-12-30) ------------------ From 78144df62e01dcc57a3e8a58e76ada17247d724f Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Sun, 27 Mar 2022 14:54:37 -0700 Subject: [PATCH 044/129] Add changelog entry for 4235291c2d5ef1f1933c81e29f365133fa331f45 --- CHANGES.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 987a241f2..201f36a19 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,10 @@ 4.3.0.dev0 (Next Release) ------------------------- +- Fixed a bug where the XML-RPC method ``supervisor.startProcess()`` would + return 500 Internal Server Error instead of an XML-RPC fault response + if the command could not be parsed. Patch by Julien Le Cléach. + - ``supervisorctl`` now reads extra files included via the ``[include]`` section in ``supervisord.conf`` like ``supervisord`` does. This allows the ``[supervisorctl]`` section or ``[ctlplugin:x]`` sections to be in From a54c29dc0770e64985b82bdaecd3b858580d67ca Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Sun, 27 Mar 2022 15:07:02 -0700 Subject: [PATCH 045/129] Add changelog entry for 2991916178679aacc8b52bf1e62841069957fd30 --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 201f36a19..c9bf80567 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ return 500 Internal Server Error instead of an XML-RPC fault response if the command could not be parsed. Patch by Julien Le Cléach. +- Fixed a bug on Python 2.7 where a ``UnicodeDecodeError`` may have occurred + when using the web interface. Patch by Vinay Sajip. + - ``supervisorctl`` now reads extra files included via the ``[include]`` section in ``supervisord.conf`` like ``supervisord`` does. This allows the ``[supervisorctl]`` section or ``[ctlplugin:x]`` sections to be in From 8a483b881d872292383d1648d8172c17eaa53579 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Sun, 27 Mar 2022 15:25:36 -0700 Subject: [PATCH 046/129] Fix indentation --- supervisor/process.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/supervisor/process.py b/supervisor/process.py index 9364aeb34..b394be812 100644 --- a/supervisor/process.py +++ b/supervisor/process.py @@ -569,9 +569,9 @@ def finish(self, pid, sts): self._assertInState(ProcessStates.STOPPING) self.change_state(ProcessStates.STOPPED) if exit_expected: - self.config.options.logger.info(msg) + self.config.options.logger.info(msg) else: - self.config.options.logger.warn(msg) + self.config.options.logger.warn(msg) elif too_quickly: From e3601a87f0b6c52b97e41dc7dbc4aaffaf0ae175 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Sun, 27 Mar 2022 15:29:02 -0700 Subject: [PATCH 047/129] Add changelog entry for 26658a5b79cf2693eb9501d29e9c68c887d82ce1 --- CHANGES.rst | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index c9bf80567..6c8901271 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -13,13 +13,17 @@ the ``[supervisorctl]`` section or ``[ctlplugin:x]`` sections to be in included files. Patch by François Granade. +- The return value of the XML-RPC method ``supervisor.getAllConfigInfo()`` + now includes the ``directory``, ``uid``, and ``serverurl`` of the + program. Patch by Yellmean. + - If ``supervisord`` searches the default paths for its config file (no ``-c`` flag given), it will now print a message showing the path of the config file that it loaded. Patch by Alexander Tuna. -- The return value of the XML-RPC method ``supervisor.getAllConfigInfo()`` - now includes the ``directory``, ``uid``, and ``serverurl`` of the - program. Patch by Yellmean. +- If a subprocess exits with a unexpected exit code (one not listed in + ``exitcodes=`` in a ``[program:x]`` section) then the exit will now be logged + at the ``WARN`` level instead of ``INFO``. Patch by Precy Lee. 4.2.4 (2021-12-30) ------------------ From 25670861248302bf3c334912acaaddf00df6fb56 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Mon, 11 Apr 2022 16:28:02 -0700 Subject: [PATCH 048/129] List names only in COPYRIGHT.txt. Closes #1501 --- COPYRIGHT.txt | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt index 20f4e35fc..ec2ec06f5 100644 --- a/COPYRIGHT.txt +++ b/COPYRIGHT.txt @@ -1,27 +1,6 @@ Supervisor is Copyright (c) 2006-2015 Agendaless Consulting and Contributors. (http://www.agendaless.com), All Rights Reserved - This software is subject to the provisions of the license at - http://www.repoze.org/LICENSE.txt . A copy of this license should - accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND - ANY AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, - BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, - MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR - PURPOSE. - medusa was (is?) Copyright (c) Sam Rushing. http_client.py code Copyright (c) by Daniel Krech, http://eikeon.com/. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - From 4d8f5018ed4abd6fd9496a4cf0926db9e0bb70df Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Tue, 19 Apr 2022 13:52:55 -0700 Subject: [PATCH 049/129] Add Python 3.9 and 3.10 to classifiers --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index fb6a31d1e..d5f0be9c1 100644 --- a/setup.py +++ b/setup.py @@ -64,6 +64,8 @@ "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", ] version_txt = os.path.join(here, 'supervisor/version.txt') From 18490196ebbc96d884d4fc30c4ef2456d3f8d1b3 Mon Sep 17 00:00:00 2001 From: Zero King Date: Sat, 30 Apr 2022 06:47:07 +0000 Subject: [PATCH 050/129] Fix use of deprecated tempfile.mktemp() in tests --- supervisor/tests/base.py | 4 ++-- supervisor/tests/test_http.py | 10 ++++---- supervisor/tests/test_options.py | 40 ++++++++++++++++---------------- 3 files changed, 28 insertions(+), 26 deletions(-) diff --git a/supervisor/tests/base.py b/supervisor/tests/base.py index 7553a2cce..f608b2bea 100644 --- a/supervisor/tests/base.py +++ b/supervisor/tests/base.py @@ -593,8 +593,8 @@ def makeExecutable(file, substitutions=None): for key in substitutions.keys(): data = data.replace('<<%s>>' % key.upper(), substitutions[key]) - tmpnam = tempfile.mktemp(prefix=last) - with open(tmpnam, 'w') as f: + with tempfile.NamedTemporaryFile(prefix=last, delete=False) as f: + tmpnam = f.name f.write(data) os.chmod(tmpnam, 0o755) return tmpnam diff --git a/supervisor/tests/test_http.py b/supervisor/tests/test_http.py index fee1f89a1..de0c42328 100644 --- a/supervisor/tests/test_http.py +++ b/supervisor/tests/test_http.py @@ -169,8 +169,8 @@ def test_handle_more_follow_file_recreated(self): def test_handle_more_follow_file_gone(self): request = DummyRequest('/logtail/foo', None, None, None) - filename = tempfile.mktemp() - with open(filename, 'wb') as f: + with tempfile.NamedTemporaryFile(delete=False) as f: + filename = f.name f.write(b'a' * 80) try: producer = self._makeOne(request, f.name, 80) @@ -620,7 +620,8 @@ def test_make_http_servers_socket_type_error(self): self.assertEqual(exc.args[0], 'Cannot determine socket type 999') def test_make_http_servers_noauth(self): - socketfile = tempfile.mktemp() + with tempfile.NamedTemporaryFile(delete=False) as f: + socketfile = f.name inet = {'family':socket.AF_INET, 'host':'localhost', 'port':17735, 'username':None, 'password':None, 'section':'inet_http_server'} unix = {'family':socket.AF_UNIX, 'file':socketfile, 'chmod':0o700, @@ -647,7 +648,8 @@ def test_make_http_servers_noauth(self): self.assertEqual([x.IDENT for x in server.handlers], idents) def test_make_http_servers_withauth(self): - socketfile = tempfile.mktemp() + with tempfile.NamedTemporaryFile(delete=False) as f: + socketfile = f.name inet = {'family':socket.AF_INET, 'host':'localhost', 'port':17736, 'username':'username', 'password':'password', 'section':'inet_http_server'} diff --git a/supervisor/tests/test_options.py b/supervisor/tests/test_options.py index 472d0e416..fae416583 100644 --- a/supervisor/tests/test_options.py +++ b/supervisor/tests/test_options.py @@ -1442,9 +1442,9 @@ def test_options_afinet_no_port(self): "section [inet_http_server] has no port value") def test_cleanup_afunix_unlink(self): - fn = tempfile.mktemp() - with open(fn, 'w') as f: - f.write('foo') + with tempfile.NamedTemporaryFile(delete=False) as f: + fn = f.name + f.write(b'foo') instance = self._makeOne() instance.unlink_socketfiles = True class Server: @@ -1456,10 +1456,10 @@ class Server: self.assertFalse(os.path.exists(fn)) def test_cleanup_afunix_nounlink(self): - fn = tempfile.mktemp() + with tempfile.NamedTemporaryFile(delete=False) as f: + fn = f.name + f.write(b'foo') try: - with open(fn, 'w') as f: - f.write('foo') instance = self._makeOne() class Server: pass @@ -1477,10 +1477,10 @@ class Server: def test_cleanup_afunix_ignores_oserror_enoent(self): notfound = os.path.join(os.path.dirname(__file__), 'notfound') - socketname = tempfile.mktemp() + with tempfile.NamedTemporaryFile(delete=False) as f: + socketname = f.name + f.write(b'foo') try: - with open(socketname, 'w') as f: - f.write('foo') instance = self._makeOne() instance.unlink_socketfiles = True class Server: @@ -1499,10 +1499,10 @@ class Server: pass def test_cleanup_removes_pidfile(self): - pidfile = tempfile.mktemp() + with tempfile.NamedTemporaryFile(delete=False) as f: + pidfile = f.name + f.write(b'2') try: - with open(pidfile, 'w') as f: - f.write('2') instance = self._makeOne() instance.pidfile = pidfile instance.logger = DummyLogger() @@ -1523,10 +1523,9 @@ def test_cleanup_pidfile_ignores_oserror_enoent(self): instance.cleanup() # shouldn't raise def test_cleanup_does_not_remove_pidfile_from_another_supervisord(self): - pidfile = tempfile.mktemp() - - with open(pidfile, 'w') as f: - f.write('1234') + with tempfile.NamedTemporaryFile(delete=False) as f: + pidfile = f.name + f.write(b'1234') try: instance = self._makeOne() @@ -1544,10 +1543,10 @@ def test_cleanup_does_not_remove_pidfile_from_another_supervisord(self): pass def test_cleanup_closes_poller(self): - pidfile = tempfile.mktemp() + with tempfile.NamedTemporaryFile(delete=False) as f: + pidfile = f.name + f.write(b'2') try: - with open(pidfile, 'w') as f: - f.write('2') instance = self._makeOne() instance.pidfile = pidfile @@ -1648,7 +1647,8 @@ def test_reopenlogs(self): self.assertEqual(logger.data[0], 'supervisord logreopen') def test_write_pidfile_ok(self): - fn = tempfile.mktemp() + with tempfile.NamedTemporaryFile(delete=False) as f: + fn = f.name try: instance = self._makeOne() instance.logger = DummyLogger() From 3f52f9a28bf518c631fbb86d6377bc4cefbbbee4 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Thu, 30 Jun 2022 13:13:20 -0700 Subject: [PATCH 051/129] Remove use of URL parsing functions deprecated in Python 3.8 Closes #1513 Closes #1519 --- CHANGES.rst | 2 ++ supervisor/xmlrpc.py | 11 +++-------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 6c8901271..3d971dda3 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -8,6 +8,8 @@ - Fixed a bug on Python 2.7 where a ``UnicodeDecodeError`` may have occurred when using the web interface. Patch by Vinay Sajip. +- Removed use of URL parsing functions deprecated in Python 3.8. + - ``supervisorctl`` now reads extra files included via the ``[include]`` section in ``supervisord.conf`` like ``supervisord`` does. This allows the ``[supervisorctl]`` section or ``[ctlplugin:x]`` sections to be in diff --git a/supervisor/xmlrpc.py b/supervisor/xmlrpc.py index 9a1488b86..025ada883 100644 --- a/supervisor/xmlrpc.py +++ b/supervisor/xmlrpc.py @@ -9,7 +9,7 @@ from supervisor.compat import xmlrpclib from supervisor.compat import StringIO -from supervisor.compat import urllib +from supervisor.compat import urlparse from supervisor.compat import as_bytes from supervisor.compat import as_string from supervisor.compat import encodestring @@ -486,13 +486,10 @@ def __init__(self, username=None, password=None, serverurl=None): self.verbose = False self.serverurl = serverurl if serverurl.startswith('http://'): - type, uri = urllib.splittype(serverurl) - host, path = urllib.splithost(uri) - host, port = urllib.splitport(host) + parsed = urlparse.urlparse(serverurl) + host, port = parsed.hostname, parsed.port if port is None: port = 80 - else: - port = int(port) def get_connection(host=host, port=port): return httplib.HTTPConnection(host, port) self._get_connection = get_connection @@ -601,5 +598,3 @@ def gettags(comment): tags.append((tag_lineno, tag, datatype, name, '\n'.join(tag_text))) return tags - - From e8743b6e93a8d12811d42ec231ce38d5a89d23b1 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Fri, 1 Jul 2022 09:48:38 -0700 Subject: [PATCH 052/129] Remove uses of stdlib asynchat, asyncore Fixes deprecation warnings on Python 3.10 Closes #1520 --- CHANGES.rst | 5 ++++- supervisor/dispatchers.py | 7 +------ supervisor/medusa/producers.py | 2 +- supervisor/tests/test_http.py | 2 +- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 3d971dda3..22cbdbe17 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -8,7 +8,10 @@ - Fixed a bug on Python 2.7 where a ``UnicodeDecodeError`` may have occurred when using the web interface. Patch by Vinay Sajip. -- Removed use of URL parsing functions deprecated in Python 3.8. +- Removed use of ``urllib.parse`` functions ``splithost``, ``splitport``, and + ``splittype`` deprecated in Python 3.8. + +- Removed use of ``asynchat`` and ``asyncore`` deprecated in Python 3.10. - ``supervisorctl`` now reads extra files included via the ``[include]`` section in ``supervisord.conf`` like ``supervisord`` does. This allows diff --git a/supervisor/dispatchers.py b/supervisor/dispatchers.py index f0e17104c..0718a5868 100644 --- a/supervisor/dispatchers.py +++ b/supervisor/dispatchers.py @@ -1,4 +1,5 @@ import errno +from supervisor.medusa.asynchat_25 import find_prefix_at_end from supervisor.medusa.asyncore_25 import compact_traceback from supervisor.compat import as_string @@ -10,12 +11,6 @@ from supervisor.states import getEventListenerStateDescription from supervisor import loggers -def find_prefix_at_end(haystack, needle): - l = len(needle) - 1 - while l and not haystack.endswith(needle[:l]): - l -= 1 - return l - class PDispatcher: """ Asyncore dispatcher for mainloop, representing a process channel (stdin, stdout, or stderr). This class is abstract. """ diff --git a/supervisor/medusa/producers.py b/supervisor/medusa/producers.py index 9570d8bd3..5e5198240 100644 --- a/supervisor/medusa/producers.py +++ b/supervisor/medusa/producers.py @@ -11,7 +11,7 @@ producer, then wrap this with the 'chunked' transfer-encoding producer. """ -from asynchat import find_prefix_at_end +from supervisor.medusa.asynchat_25 import find_prefix_at_end from supervisor.compat import as_bytes class simple_producer: diff --git a/supervisor/tests/test_http.py b/supervisor/tests/test_http.py index de0c42328..ff6df779e 100644 --- a/supervisor/tests/test_http.py +++ b/supervisor/tests/test_http.py @@ -605,7 +605,7 @@ def _make_http_servers(self, sconfigs): if socketfile is not None: os.unlink(socketfile) finally: - from asyncore import socket_map + from supervisor.medusa.asyncore_25 import socket_map socket_map.clear() return servers From 6c1a3f5ba50c1fd8c549e452ca0920f7ebf712d4 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Mon, 4 Jul 2022 13:41:20 -0700 Subject: [PATCH 053/129] Show an error message if 'shutdown' is given an arg Closes #1522 --- CHANGES.rst | 3 +++ supervisor/supervisorctl.py | 6 ++++++ supervisor/tests/test_supervisorctl.py | 10 ++++++++++ 3 files changed, 19 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 22cbdbe17..05663891b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -30,6 +30,9 @@ ``exitcodes=`` in a ``[program:x]`` section) then the exit will now be logged at the ``WARN`` level instead of ``INFO``. Patch by Precy Lee. +- ``supervisorctl shutdown`` now shows an error message if an argument is + given. + 4.2.4 (2021-12-30) ------------------ diff --git a/supervisor/supervisorctl.py b/supervisor/supervisorctl.py index 455c1e37e..5d73e4cca 100755 --- a/supervisor/supervisorctl.py +++ b/supervisor/supervisorctl.py @@ -977,6 +977,12 @@ def help_restart(self): " see reread and update.") def do_shutdown(self, arg): + if arg: + self.ctl.output('Error: shutdown accepts no arguments') + self.ctl.exitstatus = LSBInitExitStatuses.GENERIC + self.help_shutdown() + return + if self.ctl.options.interactive: yesno = raw_input('Really shut the remote supervisord process ' 'down y/N? ') diff --git a/supervisor/tests/test_supervisorctl.py b/supervisor/tests/test_supervisorctl.py index 16bdb0717..3c0e09725 100644 --- a/supervisor/tests/test_supervisorctl.py +++ b/supervisor/tests/test_supervisorctl.py @@ -1287,6 +1287,16 @@ def test_shutdown_help(self): out = plugin.ctl.stdout.getvalue() self.assertTrue("Shut the remote supervisord down" in out) + def test_shutdown_with_arg_shows_error(self): + plugin = self._makeOne() + options = plugin.ctl.options + result = plugin.do_shutdown('bad') + self.assertEqual(result, None) + self.assertEqual(options._server.supervisor._shutdown, False) + val = plugin.ctl.stdout.getvalue() + self.assertTrue(val.startswith('Error: shutdown accepts no arguments'), val) + self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC) + def test_shutdown(self): plugin = self._makeOne() options = plugin.ctl.options From 9f0c3d4e52f077abad5116255c947d87ffd7d0b8 Mon Sep 17 00:00:00 2001 From: Tim Gates Date: Fri, 15 Jul 2022 08:16:44 +1000 Subject: [PATCH 054/129] docs: fix simple typo, temporay -> temporary There is a small typo in supervisor/http.py. Should read `temporary` rather than `temporay`. --- supervisor/http.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/supervisor/http.py b/supervisor/http.py index a38086335..af3e3da87 100644 --- a/supervisor/http.py +++ b/supervisor/http.py @@ -739,7 +739,7 @@ def handle_request(self, request): if logfile is None or not os.path.exists(logfile): # we return 404 because no logfile is a temporary condition. # if the process has never been started, no logfile will exist - # on disk. a logfile of None is also a temporay condition, + # on disk. a logfile of None is also a temporary condition, # since the config file can be reloaded. request.error(404) # not found return From 906d2adec9bd27d54cdff48bdd12f6cb42bf9546 Mon Sep 17 00:00:00 2001 From: Josh Soref <2119212+jsoref@users.noreply.github.com> Date: Tue, 9 Aug 2022 00:56:44 -0400 Subject: [PATCH 055/129] Spelling (#1532) * spelling: actually Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: administratively Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: calling Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: characters Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: concatenation Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: eventlistener Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: fully Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: homogeneous Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: infinitely Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: info Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: inheritance Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: macedonia Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: the Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * spelling: version Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> --- CHANGES.rst | 8 ++++---- docs/configuration.rst | 2 +- docs/events.rst | 4 ++-- docs/subprocess.rst | 2 +- supervisor/medusa/CHANGES.txt | 2 +- supervisor/rpcinterface.py | 4 ++-- supervisor/supervisord.py | 2 +- supervisor/templating.py | 2 +- supervisor/tests/test_datatypes.py | 2 +- supervisor/tests/test_events.py | 14 +++++++------- supervisor/tests/test_process.py | 6 +++--- 11 files changed, 24 insertions(+), 24 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 05663891b..709aa41bf 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1400,7 +1400,7 @@ supervisorctl. To start a group, use ``start groupname:*``. To start multiple groups, use ``start groupname1:* groupname2:*``. Equivalent commands work for "stop" and "restart". You can mix and match short - processnames, fullly-specified group:process names, and groupsplats on the + processnames, fully-specified group:process names, and groupsplats on the same line for any of these commands. - Added ``directory`` option to process config. If you set this @@ -1456,7 +1456,7 @@ supervisor will fail to start. - The Python string expression ``%(here)s`` (referring to the directory in - which the the configuration file was found) can be used within the + which the configuration file was found) can be used within the following sections/options within the config file:: unix_http_server:file @@ -1610,7 +1610,7 @@ channel. The keys "log_stderr" and "log_stdout" have been removed. -- ``[program:x]`` config file sections now represent "homgeneous process +- ``[program:x]`` config file sections now represent "homogeneous process groups" instead of single processes. A "numprocs" key in the section represents the number of processes that are in the group. A "process_name" key in the section allows composition of the each process' name within the @@ -1725,7 +1725,7 @@ - Processes which started successfully after failing to start initially are no longer reported in BACKOFF state once they are - started successfully (thanks to Damjan from Macdonia for the bug + started successfully (thanks to Damjan from Macedonia for the bug report). - Add new 'maintail' command to supervisorctl shell, which allows diff --git a/docs/configuration.rst b/docs/configuration.rst index 26b7c8b76..47ac1c98f 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -291,7 +291,7 @@ follows. activity log. One of ``critical``, ``error``, ``warn``, ``info``, ``debug``, ``trace``, or ``blather``. Note that at log level ``debug``, the supervisord log file will record the stderr/stdout - output of its child processes and extended info info about process + output of its child processes and extended info about process state changes, which is useful for debugging a process which isn't starting properly. See also: :ref:`activity_log_levels`. diff --git a/docs/events.rst b/docs/events.rst index 8b0d01ec5..508ddb94b 100644 --- a/docs/events.rst +++ b/docs/events.rst @@ -177,7 +177,7 @@ pool The name of the event listener pool which myeventpool poolserial An integer assigned to each event by the 30 eventlistener pool which it is being sent from. No two events generated by the same - eventlister pool during the lifetime of a + eventlistener pool during the lifetime of a :program:`supervisord` process will have the same ``poolserial`` number. This value can be used to detect event ordering anomalies. @@ -375,7 +375,7 @@ including "process state change", "process communication", and these event types. In the below list, we indicate that some event types have a "body" -which is a a *token set*. A token set consists of a set of charaters +which is a a *token set*. A token set consists of a set of characters with space-separated tokens. Each token represents a key-value pair. The key and value are separated by a colon. For example: diff --git a/docs/subprocess.rst b/docs/subprocess.rst index c358818e7..15ad25273 100644 --- a/docs/subprocess.rst +++ b/docs/subprocess.rst @@ -248,7 +248,7 @@ per the following directed graph. Subprocess State Transition Graph A process is in the ``STOPPED`` state if it has been stopped -adminstratively or if it has never been started. +administratively or if it has never been started. When an autorestarting process is in the ``BACKOFF`` state, it will be automatically restarted by :program:`supervisord`. It will switch diff --git a/supervisor/medusa/CHANGES.txt b/supervisor/medusa/CHANGES.txt index b66b8ba60..1b6307405 100644 --- a/supervisor/medusa/CHANGES.txt +++ b/supervisor/medusa/CHANGES.txt @@ -16,7 +16,7 @@ Version 0.5.5: method. * [Patch #855695] Bugfix for filesys.msdos_date * [Patch from Jason Sibre] Improve performance of xmlrpc_handler - by avoiding string concatentation + by avoiding string concatenation * [Patch from Jason Sibre] Add interface to http_request for multiple headers with the same name. diff --git a/supervisor/rpcinterface.py b/supervisor/rpcinterface.py index 7b32ae8c9..6dfcd778c 100644 --- a/supervisor/rpcinterface.py +++ b/supervisor/rpcinterface.py @@ -60,7 +60,7 @@ def _update(self, text): def getAPIVersion(self): """ Return the version of the RPC API used by supervisord - @return string version version id + @return string version id """ self._update('getAPIVersion') return API_VERSION @@ -70,7 +70,7 @@ def getAPIVersion(self): def getSupervisorVersion(self): """ Return the version of the supervisor package in use by supervisord - @return string version version id + @return string version id """ self._update('getSupervisorVersion') return VERSION diff --git a/supervisor/supervisord.py b/supervisor/supervisord.py index 138732dd4..0a4f3e697 100755 --- a/supervisor/supervisord.py +++ b/supervisor/supervisord.py @@ -282,7 +282,7 @@ def reap(self, once=False, recursionguard=0): del self.options.pidhistory[pid] if not once: # keep reaping until no more kids to reap, but don't recurse - # infintely + # infinitely self.reap(once=False, recursionguard=recursionguard+1) def handle_signal(self): diff --git a/supervisor/templating.py b/supervisor/templating.py index edc9ebd42..8e018f24e 100644 --- a/supervisor/templating.py +++ b/supervisor/templating.py @@ -511,7 +511,7 @@ def replace(self, text, structure=False): parent = self.parent i = self.deparent() if i is not None: - # reduce function call overhead by not calliing self.insert + # reduce function call overhead by not calling self.insert node = Replace(text, structure) parent._children.insert(i, node) node.parent = parent diff --git a/supervisor/tests/test_datatypes.py b/supervisor/tests/test_datatypes.py index 4f610d1b4..e2801310d 100644 --- a/supervisor/tests/test_datatypes.py +++ b/supervisor/tests/test_datatypes.py @@ -19,7 +19,7 @@ def test_strips_surrounding_whitespace(self): name = " foo\t" self.assertEqual(self._callFUT(name), "foo") - def test_disallows_inner_spaces_for_eventlister_protocol(self): + def test_disallows_inner_spaces_for_eventlistener_protocol(self): name = "foo bar" self.assertRaises(ValueError, self._callFUT, name) diff --git a/supervisor/tests/test_events.py b/supervisor/tests/test_events.py index faa8be142..bd33a0cf6 100644 --- a/supervisor/tests/test_events.py +++ b/supervisor/tests/test_events.py @@ -72,7 +72,7 @@ def test_ProcessLogEvent_attributes(self): self.assertEqual(inst.pid, 2) self.assertEqual(inst.data, 3) - def test_ProcessLogEvent_inheritence(self): + def test_ProcessLogEvent_inheritance(self): from supervisor.events import ProcessLogEvent from supervisor.events import Event self.assertTrue( @@ -87,7 +87,7 @@ def test_ProcessLogStdoutEvent_attributes(self): self.assertEqual(inst.data, 3) self.assertEqual(inst.channel, 'stdout') - def test_ProcessLogStdoutEvent_inheritence(self): + def test_ProcessLogStdoutEvent_inheritance(self): from supervisor.events import ProcessLogStdoutEvent from supervisor.events import ProcessLogEvent self.assertTrue( @@ -102,7 +102,7 @@ def test_ProcessLogStderrEvent_attributes(self): self.assertEqual(inst.data, 3) self.assertEqual(inst.channel, 'stderr') - def test_ProcessLogStderrEvent_inheritence(self): + def test_ProcessLogStderrEvent_inheritance(self): from supervisor.events import ProcessLogStderrEvent from supervisor.events import ProcessLogEvent self.assertTrue( @@ -116,7 +116,7 @@ def test_ProcessCommunicationEvent_attributes(self): self.assertEqual(inst.pid, 2) self.assertEqual(inst.data, 3) - def test_ProcessCommunicationEvent_inheritence(self): + def test_ProcessCommunicationEvent_inheritance(self): from supervisor.events import ProcessCommunicationEvent from supervisor.events import Event self.assertTrue( @@ -131,7 +131,7 @@ def test_ProcessCommunicationStdoutEvent_attributes(self): self.assertEqual(inst.data, 3) self.assertEqual(inst.channel, 'stdout') - def test_ProcessCommunicationStdoutEvent_inheritence(self): + def test_ProcessCommunicationStdoutEvent_inheritance(self): from supervisor.events import ProcessCommunicationStdoutEvent from supervisor.events import ProcessCommunicationEvent self.assertTrue( @@ -147,7 +147,7 @@ def test_ProcessCommunicationStderrEvent_attributes(self): self.assertEqual(inst.data, 3) self.assertEqual(inst.channel, 'stderr') - def test_ProcessCommunicationStderrEvent_inheritence(self): + def test_ProcessCommunicationStderrEvent_inheritance(self): from supervisor.events import ProcessCommunicationStderrEvent from supervisor.events import ProcessCommunicationEvent self.assertTrue( @@ -161,7 +161,7 @@ def test_RemoteCommunicationEvent_attributes(self): self.assertEqual(inst.type, 1) self.assertEqual(inst.data, 2) - def test_RemoteCommunicationEvent_inheritence(self): + def test_RemoteCommunicationEvent_inheritance(self): from supervisor.events import RemoteCommunicationEvent from supervisor.events import Event self.assertTrue( diff --git a/supervisor/tests/test_process.py b/supervisor/tests/test_process.py index edd02b850..d9370e24c 100644 --- a/supervisor/tests/test_process.py +++ b/supervisor/tests/test_process.py @@ -783,7 +783,7 @@ def test_stop_report_laststopreport_in_future(self): # Sleep for 2 seconds time.sleep(2) - # This iteration of stop_report() should actaully trigger the report + # This iteration of stop_report() should actually trigger the report instance.stop_report() self.assertEqual(len(options.logger.data), 1) @@ -1579,7 +1579,7 @@ def subscriber(e): # Sleep for (startsecs + 1) time.sleep(test_startsecs + 1) - # This iteration of transition() should actaully trigger the state + # This iteration of transition() should actually trigger the state # transition to RUNNING process.transition() @@ -1625,7 +1625,7 @@ def subscriber(e): # Ensure process.delay has rolled backward self.assertTrue(process.delay < future_time) - # This iteration of transition() should actaully trigger the state + # This iteration of transition() should actually trigger the state # transition to STARTING process.transition() From 7b9c9e71fc1de92154e950f7e51436f60f85df1f Mon Sep 17 00:00:00 2001 From: tyong920 Date: Sun, 11 Sep 2022 01:32:44 +0800 Subject: [PATCH 056/129] Replace os.close with os.closerange --- supervisor/options.py | 6 +----- supervisor/tests/test_options.py | 12 +++--------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/supervisor/options.py b/supervisor/options.py index 20c1b07aa..53eb56eab 100644 --- a/supervisor/options.py +++ b/supervisor/options.py @@ -1326,11 +1326,7 @@ def get_socket_map(self): def cleanup_fds(self): # try to close any leaked file descriptors (for reload) start = 5 - for x in range(start, self.minfds): - try: - os.close(x) - except OSError: - pass + os.closerange(start, self.minfds) def kill(self, pid, signal): os.kill(pid, signal) diff --git a/supervisor/tests/test_options.py b/supervisor/tests/test_options.py index fae416583..4a12c0ab4 100644 --- a/supervisor/tests/test_options.py +++ b/supervisor/tests/test_options.py @@ -1561,21 +1561,15 @@ def test_cleanup_closes_poller(self): except OSError: pass - def test_cleanup_fds_closes_5_upto_minfds_ignores_oserror(self): + @patch('os.closerange', Mock()) + def test_cleanup_fds_closes_5_upto_minfds(self): instance = self._makeOne() instance.minfds = 10 - closed = [] - def close(fd): - if fd == 7: - raise OSError - closed.append(fd) - - @patch('os.close', close) def f(): instance.cleanup_fds() f() - self.assertEqual(closed, [5,6,8,9]) + os.closerange.assert_called_with(5, 10) def test_close_httpservers(self): instance = self._makeOne() From 9cfca09c207832832684f6fd28d03bfe1a16c09e Mon Sep 17 00:00:00 2001 From: YXL Date: Sat, 17 Dec 2022 00:40:59 +0800 Subject: [PATCH 057/129] docs(api): add `getAllConfigInfo` Signed-off-by: YXL --- docs/api.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/api.rst b/docs/api.rst index 226f45a6e..e3ef91147 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -321,6 +321,8 @@ Process Control same elements as the struct returned by ``getProcessInfo``. If the process table is empty, an empty array is returned. + .. automethod:: getAllConfigInfo + .. automethod:: startProcess .. automethod:: startAllProcesses From fa5c1526606a0feb16576ddb962c05548ea73c37 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Fri, 16 Dec 2022 11:22:05 -0800 Subject: [PATCH 058/129] Fix docs build on tox>=4.0.0 --- .github/workflows/main.yml | 2 +- tox.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6ba848603..7189ff8b5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -69,7 +69,7 @@ jobs: python-version: "3.8" - name: Install dependencies - run: pip install virtualenv tox + run: pip install virtualenv tox>=4.0.0 - name: Build the docs run: TOXENV=docs tox diff --git a/tox.ini b/tox.ini index 28953354b..b296a5e40 100644 --- a/tox.ini +++ b/tox.ini @@ -41,7 +41,7 @@ deps = Sphinx readme setuptools >= 18.5 -whitelist_externals = make +allowlist_externals = make commands = make -C docs html BUILDDIR={envtmpdir} "SPHINXOPTS=-W -E" python setup.py check -m -r -s From e3c3ff96a129af29cfdf75b51efd84f0a62b5511 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Fri, 23 Dec 2022 14:06:12 -0800 Subject: [PATCH 059/129] Add changelog entry for 2a92c5c2be835bab0d094ca80fb6d455876177b8 --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 709aa41bf..e76ddcb45 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -33,6 +33,9 @@ - ``supervisorctl shutdown`` now shows an error message if an argument is given. +- File descriptors are now closed using the faster ``os.closerange()`` instead + of calling ``os.close()`` in a loop. Patch by tyong920. + 4.2.4 (2021-12-30) ------------------ From 3b5f5e177ac430272c6c8c39f7d8daaac344630b Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Fri, 23 Dec 2022 16:01:43 -0800 Subject: [PATCH 060/129] Fix ResourceWarning when running tests supervisor/supervisor/http.py:581: ResourceWarning: unclosed used = self.checkused(socketname) ResourceWarning: Enable tracemalloc to get the object allocation traceback Unlinking stale socket /var/folders/4l/30_vf63152bflw8hnvyvnw_00000gn/T/tmpbpxj02zu In 18490196ebbc96d884d4fc30c4ef2456d3f8d1b3, uses of tempfile.mktemp() were replaced with tempfile.NamedTemporaryFile(). Some tests did not run as intended afterward because they expected the file to not exist on disk. --- supervisor/tests/test_http.py | 8 ++++++-- supervisor/tests/test_options.py | 4 +++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/supervisor/tests/test_http.py b/supervisor/tests/test_http.py index ff6df779e..119859762 100644 --- a/supervisor/tests/test_http.py +++ b/supervisor/tests/test_http.py @@ -620,8 +620,10 @@ def test_make_http_servers_socket_type_error(self): self.assertEqual(exc.args[0], 'Cannot determine socket type 999') def test_make_http_servers_noauth(self): - with tempfile.NamedTemporaryFile(delete=False) as f: + with tempfile.NamedTemporaryFile(delete=True) as f: socketfile = f.name + self.assertFalse(os.path.exists(socketfile)) + inet = {'family':socket.AF_INET, 'host':'localhost', 'port':17735, 'username':None, 'password':None, 'section':'inet_http_server'} unix = {'family':socket.AF_UNIX, 'file':socketfile, 'chmod':0o700, @@ -648,8 +650,10 @@ def test_make_http_servers_noauth(self): self.assertEqual([x.IDENT for x in server.handlers], idents) def test_make_http_servers_withauth(self): - with tempfile.NamedTemporaryFile(delete=False) as f: + with tempfile.NamedTemporaryFile(delete=True) as f: socketfile = f.name + self.assertFalse(os.path.exists(socketfile)) + inet = {'family':socket.AF_INET, 'host':'localhost', 'port':17736, 'username':'username', 'password':'password', 'section':'inet_http_server'} diff --git a/supervisor/tests/test_options.py b/supervisor/tests/test_options.py index 4a12c0ab4..da3ab5857 100644 --- a/supervisor/tests/test_options.py +++ b/supervisor/tests/test_options.py @@ -1641,8 +1641,10 @@ def test_reopenlogs(self): self.assertEqual(logger.data[0], 'supervisord logreopen') def test_write_pidfile_ok(self): - with tempfile.NamedTemporaryFile(delete=False) as f: + with tempfile.NamedTemporaryFile(delete=True) as f: fn = f.name + self.assertFalse(os.path.exists(fn)) + try: instance = self._makeOne() instance.logger = DummyLogger() From 41ec8972d338a2160b0915c4df04e998408ed5f9 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Fri, 23 Dec 2022 17:11:21 -0800 Subject: [PATCH 061/129] Add 4.2.5 release to changelog --- CHANGES.rst | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index e76ddcb45..df7296286 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,18 @@ 4.3.0.dev0 (Next Release) ------------------------- +- ``supervisorctl`` now reads extra files included via the ``[include]`` + section in ``supervisord.conf`` like ``supervisord`` does. This allows + the ``[supervisorctl]`` section or ``[ctlplugin:x]`` sections to be in + included files. Patch by François Granade. + +- If ``supervisord`` searches the default paths for its config file (no + ``-c`` flag given), it will now print a message showing the path of the + config file that it loaded. Patch by Alexander Tuna. + +4.2.5 (2022-12-23) +------------------ + - Fixed a bug where the XML-RPC method ``supervisor.startProcess()`` would return 500 Internal Server Error instead of an XML-RPC fault response if the command could not be parsed. Patch by Julien Le Cléach. @@ -13,19 +25,10 @@ - Removed use of ``asynchat`` and ``asyncore`` deprecated in Python 3.10. -- ``supervisorctl`` now reads extra files included via the ``[include]`` - section in ``supervisord.conf`` like ``supervisord`` does. This allows - the ``[supervisorctl]`` section or ``[ctlplugin:x]`` sections to be in - included files. Patch by François Granade. - - The return value of the XML-RPC method ``supervisor.getAllConfigInfo()`` now includes the ``directory``, ``uid``, and ``serverurl`` of the program. Patch by Yellmean. -- If ``supervisord`` searches the default paths for its config file (no - ``-c`` flag given), it will now print a message showing the path of the - config file that it loaded. Patch by Alexander Tuna. - - If a subprocess exits with a unexpected exit code (one not listed in ``exitcodes=`` in a ``[program:x]`` section) then the exit will now be logged at the ``WARN`` level instead of ``INFO``. Patch by Precy Lee. From 7932b1583839ab182e368a8d6f384f1ae4457198 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Sat, 24 Dec 2022 13:35:40 -0800 Subject: [PATCH 062/129] Run most of workflow on Ubuntu 20.04, add Python 3.11 --- .github/workflows/main.yml | 67 ++++++++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 20 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7189ff8b5..16ad87ca9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -4,67 +4,94 @@ on: [push, pull_request] jobs: tests: - runs-on: ubuntu-18.04 + runs-on: ubuntu-20.04 strategy: fail-fast: false matrix: - python-version: [2.7, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, "3.10"] + python-version: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9, "3.10", 3.11] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - - name: Show Python version - run: python -V + - name: Install dependencies + run: pip install virtualenv tox + + - name: Set variable for TOXENV based on Python version + id: toxenv + run: python -c 'import sys; print("TOXENV=py%d%d" % (sys.version_info.major, sys.version_info.minor))' | tee -a $GITHUB_OUTPUT - - name: Set TOXENV based on Python version - run: python -c 'import sys; print("TOXENV=py%d%d" % (sys.version_info.major, sys.version_info.minor))' | tee -a $GITHUB_ENV + - name: Run the unit tests + run: TOXENV=${{steps.toxenv.outputs.TOXENV}} tox + + - name: Run the end-to-end tests + run: TOXENV=${{steps.toxenv.outputs.TOXENV}} END_TO_END=1 tox + + tests_ubuntu_1804: + runs-on: ubuntu-18.04 + strategy: + fail-fast: false + matrix: + python-version: [3.4] # not supported by setup-python on ubuntu 20.04 + + steps: + - uses: actions/checkout@v3 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} - name: Install dependencies run: pip install virtualenv tox + - name: Set variable for TOXENV based on Python version + id: toxenv + run: python -c 'import sys; print("TOXENV=py%d%d" % (sys.version_info.major, sys.version_info.minor))' | tee -a $GITHUB_OUTPUT + - name: Run the unit tests - run: tox + run: TOXENV=${{steps.toxenv.outputs.TOXENV}} tox - name: Run the end-to-end tests - run: END_TO_END=1 tox + run: TOXENV=${{steps.toxenv.outputs.TOXENV}} END_TO_END=1 tox coverage: - runs-on: ubuntu-18.04 + runs-on: ubuntu-20.04 strategy: fail-fast: false matrix: - python-version: [2.7, 3.7] + python-version: [2.7, 3.8] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: pip install virtualenv tox - - name: Set TOXENV based on Python version - run: python -c 'import sys; e="cover" if sys.version_info.major == 2 else "cover3"; print("TOXENV=%s" % e)' | tee -a $GITHUB_ENV + - name: Set variable for TOXENV based on Python version + id: toxenv + run: python -c 'import sys; e="cover" if sys.version_info.major == 2 else "cover3"; print("TOXENV=%s" % e)' | tee -a $GITHUB_OUTPUT - name: Run unit test coverage - run: tox + run: TOXENV=${{steps.toxenv.outputs.TOXENV}} tox docs: - runs-on: ubuntu-18.04 + runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: "3.8" From 74e206ab133166749ceee4939396e27ff6981114 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Sat, 24 Dec 2022 13:55:08 -0800 Subject: [PATCH 063/129] Add Python 3.11 classifier --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index d5f0be9c1..9ae19f205 100644 --- a/setup.py +++ b/setup.py @@ -66,6 +66,7 @@ "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", ] version_txt = os.path.join(here, 'supervisor/version.txt') From 49b74cafb6e72e0e620e321711c1b81a0823be12 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Sun, 25 Dec 2022 10:58:24 -0800 Subject: [PATCH 064/129] Remove unused test_suite() that now causes unittest and pytest warnings supervisor/tests/test_confecho.py::test_suite /home/runner/work/supervisor/supervisor/supervisor/tests/test_confecho.py:18: DeprecationWarning: unittest.findTestCases() is deprecated and will be removed in Python 3.13. Please use unittest.TestLoader.loadTestsFromModule() instead. return unittest.findTestCases(sys.modules[__name__]) supervisor/tests/test_confecho.py::test_suite /home/runner/work/supervisor/supervisor/.tox/py311/lib/python3.11/site-packages/_pytest/python.py:199: PytestReturnNotNoneWarning: Expected None, but supervisor/tests/test_confecho.py::test_suite returned ]>]>, which will be an error in a future version of pytest. Did you mean to use `assert` instead of `return`? --- supervisor/tests/test_childutils.py | 7 ------- supervisor/tests/test_confecho.py | 7 ------- supervisor/tests/test_dispatchers.py | 6 ------ supervisor/tests/test_end_to_end.py | 6 ------ supervisor/tests/test_events.py | 7 ------- supervisor/tests/test_http.py | 6 ------ supervisor/tests/test_loggers.py | 6 ------ supervisor/tests/test_options.py | 6 ------ supervisor/tests/test_poller.py | 7 ------- supervisor/tests/test_rpcinterfaces.py | 8 -------- supervisor/tests/test_socket_manager.py | 20 -------------------- supervisor/tests/test_states.py | 7 ------- supervisor/tests/test_supervisorctl.py | 7 ------- supervisor/tests/test_supervisord.py | 7 ------- supervisor/tests/test_templating.py | 9 --------- supervisor/tests/test_web.py | 6 ------ supervisor/tests/test_xmlrpc.py | 1 - 17 files changed, 123 deletions(-) diff --git a/supervisor/tests/test_childutils.py b/supervisor/tests/test_childutils.py index f2b39d821..94193fc6c 100644 --- a/supervisor/tests/test_childutils.py +++ b/supervisor/tests/test_childutils.py @@ -132,10 +132,3 @@ def test_send(self): listener.send(msg, stdout) expected = '%s%s\n%s' % (begin, len(msg), msg) self.assertEqual(stdout.getvalue(), expected) - - -def test_suite(): - return unittest.findTestCases(sys.modules[__name__]) - -if __name__ == '__main__': - unittest.main(defaultTest='test_suite') diff --git a/supervisor/tests/test_confecho.py b/supervisor/tests/test_confecho.py index 6ae510824..f35f8455a 100644 --- a/supervisor/tests/test_confecho.py +++ b/supervisor/tests/test_confecho.py @@ -12,10 +12,3 @@ def test_main_writes_data_out_that_looks_like_a_config_file(self): output = sio.getvalue() self.assertTrue("[supervisord]" in output) - - -def test_suite(): - return unittest.findTestCases(sys.modules[__name__]) - -if __name__ == '__main__': - unittest.main(defaultTest='test_suite') diff --git a/supervisor/tests/test_dispatchers.py b/supervisor/tests/test_dispatchers.py index 87692e21f..ee6e44add 100644 --- a/supervisor/tests/test_dispatchers.py +++ b/supervisor/tests/test_dispatchers.py @@ -1227,9 +1227,3 @@ def test_ansi(self): def test_noansi(self): noansi = b'Hello world... this is longer than a token!' self.assertEqual(self._callFUT(noansi), noansi) - -def test_suite(): - return unittest.findTestCases(sys.modules[__name__]) - -if __name__ == '__main__': - unittest.main(defaultTest='test_suite') diff --git a/supervisor/tests/test_end_to_end.py b/supervisor/tests/test_end_to_end.py index dd5c977f1..763da48c7 100644 --- a/supervisor/tests/test_end_to_end.py +++ b/supervisor/tests/test_end_to_end.py @@ -419,9 +419,3 @@ def test_issue_1483c_identifier_from_command_line(self): finally: transport.close() self.assertEqual(ident, "from_command_line") - -def test_suite(): - return unittest.findTestCases(sys.modules[__name__]) - -if __name__ == '__main__': - unittest.main(defaultTest='test_suite') diff --git a/supervisor/tests/test_events.py b/supervisor/tests/test_events.py index bd33a0cf6..a432da810 100644 --- a/supervisor/tests/test_events.py +++ b/supervisor/tests/test_events.py @@ -508,10 +508,3 @@ class FooEvent(events.Event): self.assertTrue(events.EventTypes.FOO is FooEvent) finally: del events.EventTypes.FOO - -def test_suite(): - return unittest.findTestCases(sys.modules[__name__]) - -if __name__ == '__main__': - unittest.main(defaultTest='test_suite') - diff --git a/supervisor/tests/test_http.py b/supervisor/tests/test_http.py index 119859762..f4c44966f 100644 --- a/supervisor/tests/test_http.py +++ b/supervisor/tests/test_http.py @@ -684,9 +684,3 @@ def more(self): return self.data.pop(0) else: return b'' - -def test_suite(): - return unittest.findTestCases(sys.modules[__name__]) - -if __name__ == '__main__': - unittest.main(defaultTest='test_suite') diff --git a/supervisor/tests/test_loggers.py b/supervisor/tests/test_loggers.py index 0742c1766..a9ae297fd 100644 --- a/supervisor/tests/test_loggers.py +++ b/supervisor/tests/test_loggers.py @@ -599,9 +599,3 @@ def emit(self, record): self.records.append(record) def close(self): self.closed = True - -def test_suite(): - return unittest.findTestCases(sys.modules[__name__]) - -if __name__ == '__main__': - unittest.main(defaultTest='test_suite') diff --git a/supervisor/tests/test_options.py b/supervisor/tests/test_options.py index da3ab5857..4e7581478 100644 --- a/supervisor/tests/test_options.py +++ b/supervisor/tests/test_options.py @@ -3858,9 +3858,3 @@ def test_split_namespec(self): self.assertEqual(s('process'), ('process', 'process')) self.assertEqual(s('group:'), ('group', None)) self.assertEqual(s('group:*'), ('group', None)) - -def test_suite(): - return unittest.findTestCases(sys.modules[__name__]) - -if __name__ == '__main__': - unittest.main(defaultTest='test_suite') diff --git a/supervisor/tests/test_poller.py b/supervisor/tests/test_poller.py index 1b12a8e1d..fb5bf8122 100644 --- a/supervisor/tests/test_poller.py +++ b/supervisor/tests/test_poller.py @@ -437,10 +437,3 @@ class FakeKEvent(object): def __init__(self, ident, filter): self.ident = ident self.filter = filter - - -def test_suite(): - return unittest.findTestCases(sys.modules[__name__]) - -if __name__ == '__main__': - unittest.main(defaultTest='test_suite') diff --git a/supervisor/tests/test_rpcinterfaces.py b/supervisor/tests/test_rpcinterfaces.py index 0827adf05..ec88a9091 100644 --- a/supervisor/tests/test_rpcinterfaces.py +++ b/supervisor/tests/test_rpcinterfaces.py @@ -2392,14 +2392,6 @@ def test_it(self): ) - class DummyRPCInterface: def hello(self): return 'Hello!' - -def test_suite(): - return unittest.findTestCases(sys.modules[__name__]) - -if __name__ == '__main__': - unittest.main(defaultTest='test_suite') - diff --git a/supervisor/tests/test_socket_manager.py b/supervisor/tests/test_socket_manager.py index 626d78604..8eaafaa92 100644 --- a/supervisor/tests/test_socket_manager.py +++ b/supervisor/tests/test_socket_manager.py @@ -51,7 +51,6 @@ def test_on_delete(self): proxy = self._makeOne(Subject(), on_delete=self.setOnDeleteCalled) self.assertEqual(5, proxy.getValue()) proxy = None - gc_collect() self.assertTrue(self.on_deleteCalled) class ReferenceCounterTest(unittest.TestCase): @@ -94,9 +93,6 @@ def test_decr_at_zero_raises_error(self): class SocketManagerTest(unittest.TestCase): - def tearDown(self): - gc_collect() - def _getTargetClass(self): from supervisor.socket_manager import SocketManager return SocketManager @@ -160,12 +156,10 @@ def test_socket_lifecycle(self): self.assertTrue(sock_manager.is_prepared()) self.assertFalse(sock_manager.socket.close_called) sock = None - gc_collect() # Socket not actually closed yet b/c ref ct is 1 self.assertTrue(sock_manager.is_prepared()) self.assertFalse(sock_manager.socket.close_called) sock2 = None - gc_collect() # Socket closed self.assertFalse(sock_manager.is_prepared()) self.assertTrue(sock_manager.socket.close_called) @@ -178,7 +172,6 @@ def test_socket_lifecycle(self): self.assertNotEqual(sock_id, sock3_id) # Drop ref ct to zero del sock3 - gc_collect() # Now assert that socket is closed self.assertFalse(sock_manager.is_prepared()) self.assertTrue(sock_manager.socket.close_called) @@ -193,7 +186,6 @@ def test_logging(self): self.assertEqual('Creating socket %s' % repr(conf), logger.data[0]) # socket close del sock - gc_collect() self.assertEqual(len(logger.data), 2) self.assertEqual('Closing socket %s' % repr(conf), logger.data[1]) @@ -242,15 +234,3 @@ def test_close_requires_prepared_socket(self): self.fail() except Exception as e: self.assertEqual(e.args[0], 'Socket has not been prepared') - -def gc_collect(): - if __pypy__ is not None: - gc.collect() - gc.collect() - gc.collect() - -def test_suite(): - return unittest.findTestCases(sys.modules[__name__]) - -if __name__ == '__main__': - unittest.main(defaultTest='test_suite') diff --git a/supervisor/tests/test_states.py b/supervisor/tests/test_states.py index ba8e58fb7..41fed7b35 100644 --- a/supervisor/tests/test_states.py +++ b/supervisor/tests/test_states.py @@ -50,10 +50,3 @@ def test_getEventListenerStateDescription_returns_string_when_found(self): def test_getEventListenerStateDescription_returns_None_when_not_found(self): self.assertEqual(states.getEventListenerStateDescription(3.14159), None) - - -def test_suite(): - return unittest.findTestCases(sys.modules[__name__]) - -if __name__ == '__main__': - unittest.main(defaultTest='test_suite') diff --git a/supervisor/tests/test_supervisorctl.py b/supervisor/tests/test_supervisorctl.py index 3c0e09725..af2149b4c 100644 --- a/supervisor/tests/test_supervisorctl.py +++ b/supervisor/tests/test_supervisorctl.py @@ -2067,10 +2067,3 @@ def __init__(self, controller=None): def do_help(self, arg): self.helped = True - -def test_suite(): - return unittest.findTestCases(sys.modules[__name__]) - -if __name__ == '__main__': - unittest.main(defaultTest='test_suite') - diff --git a/supervisor/tests/test_supervisord.py b/supervisor/tests/test_supervisord.py index 3d7b4ffad..4099bba6c 100644 --- a/supervisor/tests/test_supervisord.py +++ b/supervisor/tests/test_supervisord.py @@ -834,10 +834,3 @@ def callback(event): self.assertEqual(supervisord.ticks[3600], 3600) self.assertEqual(len(L), 6) self.assertEqual(L[-1].__class__, events.Tick3600Event) - -def test_suite(): - return unittest.findTestCases(sys.modules[__name__]) - -if __name__ == '__main__': - unittest.main(defaultTest='test_suite') - diff --git a/supervisor/tests/test_templating.py b/supervisor/tests/test_templating.py index 29311a796..8970c4f77 100644 --- a/supervisor/tests/test_templating.py +++ b/supervisor/tests/test_templating.py @@ -1785,12 +1785,3 @@ def normalize_xml(s): s = re.sub(r"(?s)\s+<", "<", s) s = re.sub(r"(?s)>\s+", ">", s) return s - -def test_suite(): - return unittest.findTestCases(sys.modules[__name__]) - -def main(): - unittest.main(defaultTest='test_suite') - -if __name__ == '__main__': - main() diff --git a/supervisor/tests/test_web.py b/supervisor/tests/test_web.py index 8bae3eddd..f31972de7 100644 --- a/supervisor/tests/test_web.py +++ b/supervisor/tests/test_web.py @@ -177,9 +177,3 @@ def test_render_refresh(self): class DummyContext: pass - -def test_suite(): - return unittest.findTestCases(sys.modules[__name__]) - -if __name__ == '__main__': - unittest.main(defaultTest='test_suite') diff --git a/supervisor/tests/test_xmlrpc.py b/supervisor/tests/test_xmlrpc.py index 3d49ce04b..8cee058ec 100644 --- a/supervisor/tests/test_xmlrpc.py +++ b/supervisor/tests/test_xmlrpc.py @@ -917,4 +917,3 @@ def request(self, *arg, **kw): def close(self): self.closed = True - From 1e738ba190177cae66ce9c76e04991ceabe32458 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Mon, 26 Dec 2022 12:49:41 -0800 Subject: [PATCH 065/129] Disable pytest capturing so unexpected writes are seen --- tox.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tox.ini b/tox.ini index b296a5e40..79c1e4f69 100644 --- a/tox.ini +++ b/tox.ini @@ -10,7 +10,7 @@ deps = mock >= 0.5.0 passenv = END_TO_END commands = - pytest {posargs} + pytest --capture=no {posargs} [testenv:py27-configparser] ;see https://github.com/Supervisor/supervisor/issues/1230 @@ -24,7 +24,7 @@ commands = {[testenv]commands} [testenv:cover] basepython = python2.7 commands = - pytest --cov=supervisor --cov-report=term-missing --cov-report=xml {posargs} + pytest --capture=no --cov=supervisor --cov-report=term-missing --cov-report=xml {posargs} deps = {[testenv]deps} pytest-cov @@ -32,7 +32,7 @@ deps = [testenv:cover3] basepython = python3.7 commands = - pytest --cov=supervisor --cov-report=term-missing --cov-report=xml {posargs} + pytest --capture=no --cov=supervisor --cov-report=term-missing --cov-report=xml {posargs} deps = {[testenv:cover]deps} From f8bb4b1a2e233c69f6e4ef004fd6a465f3871eb0 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Mon, 26 Dec 2022 12:57:12 -0800 Subject: [PATCH 066/129] Fix unintended write to stdout - When supervisord is run as a daemon, stdout is not available. - The extra output may interfere with programs that scrape supervisorctl. - When tests are run, unintended output is shown. To solve the above issues, we need to surface this information in a different way, e.g. in the supervisord log file or behind a command line option for supervisorctl. Reverts 76779991b3cb949b9cd8ba233ebb8e254ebcac1d Reverts 44b288785f8905a414dac04e1488f03e1217ecba --- CHANGES.rst | 4 ---- supervisor/options.py | 1 - 2 files changed, 5 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index df7296286..41913ef82 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -6,10 +6,6 @@ the ``[supervisorctl]`` section or ``[ctlplugin:x]`` sections to be in included files. Patch by François Granade. -- If ``supervisord`` searches the default paths for its config file (no - ``-c`` flag given), it will now print a message showing the path of the - config file that it loaded. Patch by Alexander Tuna. - 4.2.5 (2022-12-23) ------------------ diff --git a/supervisor/options.py b/supervisor/options.py index 53eb56eab..612b2fb88 100644 --- a/supervisor/options.py +++ b/supervisor/options.py @@ -128,7 +128,6 @@ def default_configfile(self): for path in self.searchpaths: if os.path.exists(path): config = path - self.stdout.write("Chose default config file: %s\n" % config) break if config is None and self.require_configfile: self.usage('No config file found at default paths (%s); ' From 1db801783cccb021e4c3fd983e780e8d34e506b8 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Mon, 26 Dec 2022 13:09:01 -0800 Subject: [PATCH 067/129] Remove unused imports --- supervisor/tests/test_confecho.py | 1 - supervisor/tests/test_dispatchers.py | 1 - supervisor/tests/test_events.py | 1 - supervisor/tests/test_http.py | 1 - supervisor/tests/test_poller.py | 1 - supervisor/tests/test_rpcinterfaces.py | 1 - supervisor/tests/test_socket_manager.py | 2 -- supervisor/tests/test_states.py | 1 - supervisor/tests/test_supervisorctl.py | 1 - supervisor/tests/test_templating.py | 1 - supervisor/tests/test_web.py | 1 - 11 files changed, 12 deletions(-) diff --git a/supervisor/tests/test_confecho.py b/supervisor/tests/test_confecho.py index f35f8455a..975bbd59d 100644 --- a/supervisor/tests/test_confecho.py +++ b/supervisor/tests/test_confecho.py @@ -1,6 +1,5 @@ """Test suite for supervisor.confecho""" -import sys import unittest from supervisor.compat import StringIO from supervisor import confecho diff --git a/supervisor/tests/test_dispatchers.py b/supervisor/tests/test_dispatchers.py index ee6e44add..92b43bad1 100644 --- a/supervisor/tests/test_dispatchers.py +++ b/supervisor/tests/test_dispatchers.py @@ -1,6 +1,5 @@ import unittest import os -import sys from supervisor.compat import as_bytes diff --git a/supervisor/tests/test_events.py b/supervisor/tests/test_events.py index a432da810..0498f7f62 100644 --- a/supervisor/tests/test_events.py +++ b/supervisor/tests/test_events.py @@ -1,4 +1,3 @@ -import sys import unittest from supervisor.tests.base import DummyOptions diff --git a/supervisor/tests/test_http.py b/supervisor/tests/test_http.py index f4c44966f..7409f6224 100644 --- a/supervisor/tests/test_http.py +++ b/supervisor/tests/test_http.py @@ -1,7 +1,6 @@ import base64 import os import stat -import sys import socket import tempfile import unittest diff --git a/supervisor/tests/test_poller.py b/supervisor/tests/test_poller.py index fb5bf8122..6d6049e92 100644 --- a/supervisor/tests/test_poller.py +++ b/supervisor/tests/test_poller.py @@ -1,4 +1,3 @@ -import sys import unittest import errno import select diff --git a/supervisor/tests/test_rpcinterfaces.py b/supervisor/tests/test_rpcinterfaces.py index ec88a9091..ef2f83ac5 100644 --- a/supervisor/tests/test_rpcinterfaces.py +++ b/supervisor/tests/test_rpcinterfaces.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- import unittest -import sys import operator import os import time diff --git a/supervisor/tests/test_socket_manager.py b/supervisor/tests/test_socket_manager.py index 8eaafaa92..4f20bca4f 100644 --- a/supervisor/tests/test_socket_manager.py +++ b/supervisor/tests/test_socket_manager.py @@ -1,7 +1,5 @@ """Test suite for supervisor.socket_manager""" -import gc -import sys import os import unittest import socket diff --git a/supervisor/tests/test_states.py b/supervisor/tests/test_states.py index 41fed7b35..93b6c49cf 100644 --- a/supervisor/tests/test_states.py +++ b/supervisor/tests/test_states.py @@ -1,6 +1,5 @@ """Test suite for supervisor.states""" -import sys import unittest from supervisor import states diff --git a/supervisor/tests/test_supervisorctl.py b/supervisor/tests/test_supervisorctl.py index af2149b4c..04c1840c8 100644 --- a/supervisor/tests/test_supervisorctl.py +++ b/supervisor/tests/test_supervisorctl.py @@ -1,4 +1,3 @@ -import sys import unittest from supervisor import xmlrpc from supervisor.compat import StringIO diff --git a/supervisor/tests/test_templating.py b/supervisor/tests/test_templating.py index 8970c4f77..bf35fb9fa 100644 --- a/supervisor/tests/test_templating.py +++ b/supervisor/tests/test_templating.py @@ -6,7 +6,6 @@ import unittest import re -import sys _SIMPLE_XML = r""" diff --git a/supervisor/tests/test_web.py b/supervisor/tests/test_web.py index f31972de7..af04abb81 100644 --- a/supervisor/tests/test_web.py +++ b/supervisor/tests/test_web.py @@ -1,4 +1,3 @@ -import sys import unittest from supervisor.tests.base import DummySupervisor From 03730e29e5cfb0bc115c76415b17ce96343f8f8b Mon Sep 17 00:00:00 2001 From: Marti Raudsepp Date: Tue, 27 Dec 2022 11:09:04 +0200 Subject: [PATCH 068/129] Add changelog URL to package metadata When Renovate bot creates an automated pull request to update Poetry, it can automatically include the link to changelog so it's easier to find out what changed. --- setup.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 9ae19f205..b890834e7 100644 --- a/setup.py +++ b/setup.py @@ -78,6 +78,9 @@ version=supervisor_version, license='BSD-derived (http://www.repoze.org/LICENSE.txt)', url='http://supervisord.org/', + project_urls={ + 'Changelog': 'http://supervisord.org/changes.html', + }, description="A system for controlling process state under UNIX", long_description=README + '\n\n' + CHANGES, classifiers=CLASSIFIERS, @@ -87,7 +90,7 @@ install_requires=requires, extras_require={ 'testing': testing_extras, - }, + }, tests_require=tests_require, include_package_data=True, zip_safe=False, From a7cb60d58b5eb610feb76c675208f87501d4bc4b Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Tue, 27 Dec 2022 10:49:29 -0800 Subject: [PATCH 069/129] Add links for PyPI "Project Links" sidebar. Refs #1561 --- setup.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b890834e7..ac4373cae 100644 --- a/setup.py +++ b/setup.py @@ -79,7 +79,9 @@ license='BSD-derived (http://www.repoze.org/LICENSE.txt)', url='http://supervisord.org/', project_urls={ - 'Changelog': 'http://supervisord.org/changes.html', + 'Changelog': 'http://supervisord.org/changelog', + 'Documentation': 'http://supervisord.org', + 'Issue Tracker': 'https://github.com/Supervisor/supervisor', }, description="A system for controlling process state under UNIX", long_description=README + '\n\n' + CHANGES, From 0323a9ab2b8282994e95472cae1d78da7c3aa59d Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Sun, 5 Mar 2023 15:10:57 -0800 Subject: [PATCH 070/129] Fix Python interpreter used by cover3 test env This has been broken since 7932b1583839ab182e368a8d6f384f1ae4457198 but it was not noticed because prior to tox 4.2.5 (2023-01-06), tox used to return exitstatus 0 when all envs were skipped. --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 79c1e4f69..b495fb11d 100644 --- a/tox.ini +++ b/tox.ini @@ -30,7 +30,7 @@ deps = pytest-cov [testenv:cover3] -basepython = python3.7 +basepython = python3.8 commands = pytest --capture=no --cov=supervisor --cov-report=term-missing --cov-report=xml {posargs} deps = From 7d77fd56595466364f13676ad9242990b6c287d3 Mon Sep 17 00:00:00 2001 From: Ofek Lev Date: Thu, 2 Mar 2023 16:19:00 -0500 Subject: [PATCH 071/129] Replace runtime dependency on setuptools with modern libraries --- docs/configuration.rst | 8 ++--- docs/events.rst | 7 ++--- setup.py | 6 ++-- supervisor/confecho.py | 9 ++++-- supervisor/options.py | 19 ++++++----- supervisor/tests/test_end_to_end.py | 49 +++++++++++++++-------------- 6 files changed, 53 insertions(+), 45 deletions(-) diff --git a/docs/configuration.rst b/docs/configuration.rst index 47ac1c98f..9029c2b4d 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -1478,9 +1478,9 @@ sections do not have. ``result_handler`` - A `pkg_resources entry point string - `_ that - resolves to a Python callable. The default value is + An `entry point object reference + `_ + string that resolves to a Python callable. The default value is ``supervisor.dispatchers:default_handler``. Specifying an alternate result handler is a very uncommon thing to need to do, and as a result, how to create one is not documented. @@ -1581,7 +1581,7 @@ And a section in the config file meant to configure it. ``supervisor.rpcinterface_factory`` - ``pkg_resources`` "entry point" dotted name to your RPC interface's + ``entry point object reference`` dotted name to your RPC interface's factory function. *Default*: N/A diff --git a/docs/events.rst b/docs/events.rst index 508ddb94b..292528d9e 100644 --- a/docs/events.rst +++ b/docs/events.rst @@ -93,9 +93,9 @@ follows. An advanced feature, specifying an alternate "result handler" for a pool, can be specified via the ``result_handler`` parameter of an - ``[eventlistener:x]`` section in the form of a `pkg_resources - `_ "entry - point" string. The default result handler is + ``[eventlistener:x]`` section in the form of an `entry point object reference + `_ + string. The default result handler is ``supervisord.dispatchers:default_handler``. Creating an alternate result handler is not currently documented. @@ -897,4 +897,3 @@ Indicates that a process group has been removed from Supervisor's configuration. .. code-block:: text groupname:cat - diff --git a/setup.py b/setup.py index ac4373cae..d6449c900 100644 --- a/setup.py +++ b/setup.py @@ -22,8 +22,10 @@ elif (3, 0) < py_version < (3, 4): raise RuntimeError('On Python 3, Supervisor requires Python 3.4 or later') -# pkg_resource is used in several places -requires = ["setuptools"] +requires = [ + "importlib-metadata; python_version < '3.8'", + "importlib-resources; python_version < '3.9'", +] tests_require = [] if py_version < (3, 3): tests_require.append('mock<4.0.0.dev0') diff --git a/supervisor/confecho.py b/supervisor/confecho.py index c137b75a5..2bcafe200 100644 --- a/supervisor/confecho.py +++ b/supervisor/confecho.py @@ -1,7 +1,12 @@ -import pkg_resources import sys from supervisor.compat import as_string +if sys.version_info >= (3, 9): + from importlib.resources import files as resource_files +else: + from importlib_resources import files as resource_files + + def main(out=sys.stdout): - config = pkg_resources.resource_string(__name__, 'skel/sample.conf') + config = resource_files(__package__).joinpath('skel/sample.conf').read_text(encoding='utf-8') out.write(as_string(config)) diff --git a/supervisor/options.py b/supervisor/options.py index 612b2fb88..99a7b6b28 100644 --- a/supervisor/options.py +++ b/supervisor/options.py @@ -10,7 +10,6 @@ import grp import resource import stat -import pkg_resources import glob import platform import warnings @@ -55,6 +54,12 @@ from supervisor import xmlrpc from supervisor import poller +if sys.version_info >= (3, 8): + from importlib.metadata import EntryPoint +else: + from importlib_metadata import EntryPoint + + def _read_version_txt(): mydir = os.path.abspath(os.path.dirname(__file__)) version_txt = os.path.join(mydir, 'version.txt') @@ -377,7 +382,7 @@ def get_plugins(self, parser, factory_key, section_prefix): (section, factory_key)) try: factory = self.import_spec(factory_spec) - except ImportError: + except (AttributeError, ImportError): raise ValueError('%s cannot be resolved within [%s]' % ( factory_spec, section)) @@ -390,13 +395,7 @@ def get_plugins(self, parser, factory_key, section_prefix): return factories def import_spec(self, spec): - ep = pkg_resources.EntryPoint.parse("x=" + spec) - if hasattr(ep, 'resolve'): - # this is available on setuptools >= 10.2 - return ep.resolve() - else: - # this causes a DeprecationWarning on setuptools >= 11.3 - return ep.load(False) + return EntryPoint(None, spec, None).load() def read_include_config(self, fp, parser, expansions): if parser.has_section('include'): @@ -759,7 +758,7 @@ def get(section, opt, default, **kwargs): 'supervisor.dispatchers:default_handler') try: result_handler = self.import_spec(result_handler) - except ImportError: + except (AttributeError, ImportError): raise ValueError('%s cannot be resolved within [%s]' % ( result_handler, section)) diff --git a/supervisor/tests/test_end_to_end.py b/supervisor/tests/test_end_to_end.py index 763da48c7..edf0fad89 100644 --- a/supervisor/tests/test_end_to_end.py +++ b/supervisor/tests/test_end_to_end.py @@ -5,10 +5,13 @@ import signal import sys import unittest -import pkg_resources from supervisor.compat import xmlrpclib from supervisor.xmlrpc import SupervisorTransport +if sys.version_info >= (3, 9): + from importlib.resources import files as resource_files +else: + from importlib_resources import files as resource_files # end-to-test tests are slow so only run them when asked if 'END_TO_END' in os.environ: @@ -26,7 +29,7 @@ def test_issue_291a_percent_signs_in_original_env_are_preserved(self): passed to the child without the percent sign being mangled.""" key = "SUPERVISOR_TEST_1441B" val = "foo_%s_%_%%_%%%_%2_bar" - filename = pkg_resources.resource_filename(__name__, 'fixtures/issue-291a.conf') + filename = str(resource_files(__package__).joinpath('fixtures/issue-291a.conf')) args = ['-m', 'supervisor.supervisord', '-c', filename] try: os.environ[key] = val @@ -39,7 +42,7 @@ def test_issue_291a_percent_signs_in_original_env_are_preserved(self): def test_issue_550(self): """When an environment variable is set in the [supervisord] section, it should be put into the environment of the subprocess.""" - filename = pkg_resources.resource_filename(__name__, 'fixtures/issue-550.conf') + filename = str(resource_files(__package__).joinpath('fixtures/issue-550.conf')) args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -55,7 +58,7 @@ def test_issue_550(self): def test_issue_565(self): """When a log file has Unicode characters in it, 'supervisorctl tail -f name' should still work.""" - filename = pkg_resources.resource_filename(__name__, 'fixtures/issue-565.conf') + filename = str(resource_files(__package__).joinpath('fixtures/issue-565.conf')) args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -72,7 +75,7 @@ def test_issue_565(self): def test_issue_638(self): """When a process outputs something on its stdout or stderr file descriptor that is not valid UTF-8, supervisord should not crash.""" - filename = pkg_resources.resource_filename(__name__, 'fixtures/issue-638.conf') + filename = str(resource_files(__package__).joinpath('fixtures/issue-638.conf')) args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -91,7 +94,7 @@ def test_issue_638(self): def test_issue_663(self): """When Supervisor is run on Python 3, the eventlistener protocol should work.""" - filename = pkg_resources.resource_filename(__name__, 'fixtures/issue-663.conf') + filename = str(resource_files(__package__).joinpath('fixtures/issue-663.conf')) args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -103,7 +106,7 @@ def test_issue_664(self): """When a subprocess name has Unicode characters, 'supervisord' should not send incomplete XML-RPC responses and 'supervisorctl status' should work.""" - filename = pkg_resources.resource_filename(__name__, 'fixtures/issue-664.conf') + filename = str(resource_files(__package__).joinpath('fixtures/issue-664.conf')) args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -122,7 +125,7 @@ def test_issue_664(self): def test_issue_733(self): """When a subprocess enters the FATAL state, a one-line eventlistener can be used to signal supervisord to shut down.""" - filename = pkg_resources.resource_filename(__name__, 'fixtures/issue-733.conf') + filename = str(resource_files(__package__).joinpath('fixtures/issue-733.conf')) args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -131,7 +134,7 @@ def test_issue_733(self): supervisord.expect(pexpect.EOF) def test_issue_835(self): - filename = pkg_resources.resource_filename(__name__, 'fixtures/issue-835.conf') + filename = str(resource_files(__package__).joinpath('fixtures/issue-835.conf')) args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -147,7 +150,7 @@ def test_issue_835(self): transport.connection.close() def test_issue_836(self): - filename = pkg_resources.resource_filename(__name__, 'fixtures/issue-836.conf') + filename = str(resource_files(__package__).joinpath('fixtures/issue-836.conf')) args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -170,7 +173,7 @@ def test_issue_836(self): def test_issue_986_command_string_with_double_percent(self): """A percent sign can be used in a command= string without being expanded if it is escaped by a second percent sign.""" - filename = pkg_resources.resource_filename(__name__, 'fixtures/issue-986.conf') + filename = str(resource_files(__package__).joinpath('fixtures/issue-986.conf')) args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -179,7 +182,7 @@ def test_issue_986_command_string_with_double_percent(self): def test_issue_1054(self): """When run on Python 3, the 'supervisorctl avail' command should work.""" - filename = pkg_resources.resource_filename(__name__, 'fixtures/issue-1054.conf') + filename = str(resource_files(__package__).joinpath('fixtures/issue-1054.conf')) args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -197,7 +200,7 @@ def test_issue_1170a(self): """When the [supervisord] section has a variable defined in environment=, that variable should be able to be used in an %(ENV_x) expansion in a [program] section.""" - filename = pkg_resources.resource_filename(__name__, 'fixtures/issue-1170a.conf') + filename = str(resource_files(__package__).joinpath('fixtures/issue-1170a.conf')) args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -208,7 +211,7 @@ def test_issue_1170b(self): environment=, and a variable by the same name is defined in enviroment= of a [program] section, the one in the [program] section should be used.""" - filename = pkg_resources.resource_filename(__name__, 'fixtures/issue-1170b.conf') + filename = str(resource_files(__package__).joinpath('fixtures/issue-1170b.conf')) args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -219,7 +222,7 @@ def test_issue_1170c(self): environment=, and a variable by the same name is defined in enviroment= of an [eventlistener] section, the one in the [eventlistener] section should be used.""" - filename = pkg_resources.resource_filename(__name__, 'fixtures/issue-1170c.conf') + filename = str(resource_files(__package__).joinpath('fixtures/issue-1170c.conf')) args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -230,7 +233,7 @@ def test_issue_1224(self): then the non-rotating logger will be used to avoid an IllegalSeekError in the case that the user has configured a non-seekable file like /dev/stdout.""" - filename = pkg_resources.resource_filename(__name__, 'fixtures/issue-1224.conf') + filename = str(resource_files(__package__).joinpath('fixtures/issue-1224.conf')) args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -239,7 +242,7 @@ def test_issue_1224(self): def test_issue_1231a(self): """When 'supervisorctl tail -f name' is run and the log contains unicode, it should not fail.""" - filename = pkg_resources.resource_filename(__name__, 'fixtures/issue-1231a.conf') + filename = str(resource_files(__package__).joinpath('fixtures/issue-1231a.conf')) args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -256,7 +259,7 @@ def test_issue_1231a(self): def test_issue_1231b(self): """When 'supervisorctl tail -f name' is run and the log contains unicode, it should not fail.""" - filename = pkg_resources.resource_filename(__name__, 'fixtures/issue-1231b.conf') + filename = str(resource_files(__package__).joinpath('fixtures/issue-1231b.conf')) args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -290,7 +293,7 @@ def test_issue_1231b(self): def test_issue_1231c(self): """When 'supervisorctl tail -f name' is run and the log contains unicode, it should not fail.""" - filename = pkg_resources.resource_filename(__name__, 'fixtures/issue-1231c.conf') + filename = str(resource_files(__package__).joinpath('fixtures/issue-1231c.conf')) args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -332,7 +335,7 @@ def test_issue_1298(self): """When the output of 'supervisorctl tail -f worker' is piped such as 'supervisor tail -f worker | grep something', 'supervisorctl' should not crash.""" - filename = pkg_resources.resource_filename(__name__, 'fixtures/issue-1298.conf') + filename = str(resource_files(__package__).joinpath('fixtures/issue-1298.conf')) args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -366,7 +369,7 @@ def test_issue_1418_pidproxy_cmd_with_args(self): def test_issue_1483a_identifier_default(self): """When no identifier is supplied on the command line or in the config file, the default is used.""" - filename = pkg_resources.resource_filename(__name__, 'fixtures/issue-1483a.conf') + filename = str(resource_files(__package__).joinpath('fixtures/issue-1483a.conf')) args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -385,7 +388,7 @@ def test_issue_1483a_identifier_default(self): def test_issue_1483b_identifier_from_config_file(self): """When the identifier is supplied in the config file only, that identifier is used instead of the default.""" - filename = pkg_resources.resource_filename(__name__, 'fixtures/issue-1483b.conf') + filename = str(resource_files(__package__).joinpath('fixtures/issue-1483b.conf')) args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -404,7 +407,7 @@ def test_issue_1483b_identifier_from_config_file(self): def test_issue_1483c_identifier_from_command_line(self): """When an identifier is supplied in both the config file and on the command line, the one from the command line is used.""" - filename = pkg_resources.resource_filename(__name__, 'fixtures/issue-1483c.conf') + filename = str(resource_files(__package__).joinpath('fixtures/issue-1483c.conf')) args = ['-m', 'supervisor.supervisord', '-c', filename, '-i', 'from_command_line'] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) From 281fd784bd7445fcbc4a50c7593b0519af9e84e3 Mon Sep 17 00:00:00 2001 From: Ofek Lev Date: Sun, 5 Mar 2023 13:18:23 -0500 Subject: [PATCH 072/129] refactor --- setup.py | 2 +- supervisor/confecho.py | 8 ++--- supervisor/resources.py | 40 +++++++++++++++++++++++ supervisor/tests/test_end_to_end.py | 50 +++++++++++++---------------- 4 files changed, 66 insertions(+), 34 deletions(-) create mode 100644 supervisor/resources.py diff --git a/setup.py b/setup.py index d6449c900..8ef9f7bb7 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ requires = [ "importlib-metadata; python_version < '3.8'", - "importlib-resources; python_version < '3.9'", + "importlib-resources; python_version < '3.7'", ] tests_require = [] if py_version < (3, 3): diff --git a/supervisor/confecho.py b/supervisor/confecho.py index 2bcafe200..09a40ebce 100644 --- a/supervisor/confecho.py +++ b/supervisor/confecho.py @@ -1,12 +1,8 @@ import sys +from supervisor import resources from supervisor.compat import as_string -if sys.version_info >= (3, 9): - from importlib.resources import files as resource_files -else: - from importlib_resources import files as resource_files - def main(out=sys.stdout): - config = resource_files(__package__).joinpath('skel/sample.conf').read_text(encoding='utf-8') + config = resources.read_text(__package__, 'skel/sample.conf') out.write(as_string(config)) diff --git a/supervisor/resources.py b/supervisor/resources.py new file mode 100644 index 000000000..ef2a06b6f --- /dev/null +++ b/supervisor/resources.py @@ -0,0 +1,40 @@ +import sys + +if sys.version_info >= (3, 9): + from importlib.resources import files + + + def read_text(package, path): + return files(package).joinpath(path).read_text(encoding='utf-8') + + + def find(package, path): + return str(files(package).joinpath(path)) + +elif sys.version_info >= (3, 7): + import importlib.resources + + + def read_text(package, path): + with importlib.resources.path(package, '__init__.py') as p: + return p.parent.joinpath(path).read_text(encoding='utf-8') + + + def find(package, path): + with importlib.resources.path(package, '__init__.py') as p: + return str(p.parent.joinpath(path)) + +else: + from io import open + + import importlib_resources + + + def read_text(package, path): + with open(find(package, path), 'r', encoding='utf-8') as f: + return f.read() + + + def find(package, path): + with importlib_resources.path(package, '__init__.py') as p: + return str(p.parent.joinpath(path)) diff --git a/supervisor/tests/test_end_to_end.py b/supervisor/tests/test_end_to_end.py index edf0fad89..27622e947 100644 --- a/supervisor/tests/test_end_to_end.py +++ b/supervisor/tests/test_end_to_end.py @@ -5,14 +5,10 @@ import signal import sys import unittest +from supervisor import resources from supervisor.compat import xmlrpclib from supervisor.xmlrpc import SupervisorTransport -if sys.version_info >= (3, 9): - from importlib.resources import files as resource_files -else: - from importlib_resources import files as resource_files - # end-to-test tests are slow so only run them when asked if 'END_TO_END' in os.environ: import pexpect @@ -29,7 +25,7 @@ def test_issue_291a_percent_signs_in_original_env_are_preserved(self): passed to the child without the percent sign being mangled.""" key = "SUPERVISOR_TEST_1441B" val = "foo_%s_%_%%_%%%_%2_bar" - filename = str(resource_files(__package__).joinpath('fixtures/issue-291a.conf')) + filename = resources.find(__package__, 'fixtures/issue-291a.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] try: os.environ[key] = val @@ -42,7 +38,7 @@ def test_issue_291a_percent_signs_in_original_env_are_preserved(self): def test_issue_550(self): """When an environment variable is set in the [supervisord] section, it should be put into the environment of the subprocess.""" - filename = str(resource_files(__package__).joinpath('fixtures/issue-550.conf')) + filename = resources.find(__package__, 'fixtures/issue-550.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -58,7 +54,7 @@ def test_issue_550(self): def test_issue_565(self): """When a log file has Unicode characters in it, 'supervisorctl tail -f name' should still work.""" - filename = str(resource_files(__package__).joinpath('fixtures/issue-565.conf')) + filename = resources.find(__package__, 'fixtures/issue-565.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -75,7 +71,7 @@ def test_issue_565(self): def test_issue_638(self): """When a process outputs something on its stdout or stderr file descriptor that is not valid UTF-8, supervisord should not crash.""" - filename = str(resource_files(__package__).joinpath('fixtures/issue-638.conf')) + filename = resources.find(__package__, 'fixtures/issue-638.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -94,7 +90,7 @@ def test_issue_638(self): def test_issue_663(self): """When Supervisor is run on Python 3, the eventlistener protocol should work.""" - filename = str(resource_files(__package__).joinpath('fixtures/issue-663.conf')) + filename = resources.find(__package__, 'fixtures/issue-663.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -106,7 +102,7 @@ def test_issue_664(self): """When a subprocess name has Unicode characters, 'supervisord' should not send incomplete XML-RPC responses and 'supervisorctl status' should work.""" - filename = str(resource_files(__package__).joinpath('fixtures/issue-664.conf')) + filename = resources.find(__package__, 'fixtures/issue-664.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -125,7 +121,7 @@ def test_issue_664(self): def test_issue_733(self): """When a subprocess enters the FATAL state, a one-line eventlistener can be used to signal supervisord to shut down.""" - filename = str(resource_files(__package__).joinpath('fixtures/issue-733.conf')) + filename = resources.find(__package__, 'fixtures/issue-733.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -134,7 +130,7 @@ def test_issue_733(self): supervisord.expect(pexpect.EOF) def test_issue_835(self): - filename = str(resource_files(__package__).joinpath('fixtures/issue-835.conf')) + filename = resources.find(__package__, 'fixtures/issue-835.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -150,7 +146,7 @@ def test_issue_835(self): transport.connection.close() def test_issue_836(self): - filename = str(resource_files(__package__).joinpath('fixtures/issue-836.conf')) + filename = resources.find(__package__, 'fixtures/issue-836.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -173,7 +169,7 @@ def test_issue_836(self): def test_issue_986_command_string_with_double_percent(self): """A percent sign can be used in a command= string without being expanded if it is escaped by a second percent sign.""" - filename = str(resource_files(__package__).joinpath('fixtures/issue-986.conf')) + filename = resources.find(__package__, 'fixtures/issue-986.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -182,7 +178,7 @@ def test_issue_986_command_string_with_double_percent(self): def test_issue_1054(self): """When run on Python 3, the 'supervisorctl avail' command should work.""" - filename = str(resource_files(__package__).joinpath('fixtures/issue-1054.conf')) + filename = resources.find(__package__, 'fixtures/issue-1054.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -200,7 +196,7 @@ def test_issue_1170a(self): """When the [supervisord] section has a variable defined in environment=, that variable should be able to be used in an %(ENV_x) expansion in a [program] section.""" - filename = str(resource_files(__package__).joinpath('fixtures/issue-1170a.conf')) + filename = resources.find(__package__, 'fixtures/issue-1170a.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -211,7 +207,7 @@ def test_issue_1170b(self): environment=, and a variable by the same name is defined in enviroment= of a [program] section, the one in the [program] section should be used.""" - filename = str(resource_files(__package__).joinpath('fixtures/issue-1170b.conf')) + filename = resources.find(__package__, 'fixtures/issue-1170b.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -222,7 +218,7 @@ def test_issue_1170c(self): environment=, and a variable by the same name is defined in enviroment= of an [eventlistener] section, the one in the [eventlistener] section should be used.""" - filename = str(resource_files(__package__).joinpath('fixtures/issue-1170c.conf')) + filename = resources.find(__package__, 'fixtures/issue-1170c.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -233,7 +229,7 @@ def test_issue_1224(self): then the non-rotating logger will be used to avoid an IllegalSeekError in the case that the user has configured a non-seekable file like /dev/stdout.""" - filename = str(resource_files(__package__).joinpath('fixtures/issue-1224.conf')) + filename = resources.find(__package__, 'fixtures/issue-1224.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -242,7 +238,7 @@ def test_issue_1224(self): def test_issue_1231a(self): """When 'supervisorctl tail -f name' is run and the log contains unicode, it should not fail.""" - filename = str(resource_files(__package__).joinpath('fixtures/issue-1231a.conf')) + filename = resources.find(__package__, 'fixtures/issue-1231a.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -259,7 +255,7 @@ def test_issue_1231a(self): def test_issue_1231b(self): """When 'supervisorctl tail -f name' is run and the log contains unicode, it should not fail.""" - filename = str(resource_files(__package__).joinpath('fixtures/issue-1231b.conf')) + filename = resources.find(__package__, 'fixtures/issue-1231b.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -293,7 +289,7 @@ def test_issue_1231b(self): def test_issue_1231c(self): """When 'supervisorctl tail -f name' is run and the log contains unicode, it should not fail.""" - filename = str(resource_files(__package__).joinpath('fixtures/issue-1231c.conf')) + filename = resources.find(__package__, 'fixtures/issue-1231c.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -335,7 +331,7 @@ def test_issue_1298(self): """When the output of 'supervisorctl tail -f worker' is piped such as 'supervisor tail -f worker | grep something', 'supervisorctl' should not crash.""" - filename = str(resource_files(__package__).joinpath('fixtures/issue-1298.conf')) + filename = resources.find(__package__, 'fixtures/issue-1298.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -369,7 +365,7 @@ def test_issue_1418_pidproxy_cmd_with_args(self): def test_issue_1483a_identifier_default(self): """When no identifier is supplied on the command line or in the config file, the default is used.""" - filename = str(resource_files(__package__).joinpath('fixtures/issue-1483a.conf')) + filename = resources.find(__package__, 'fixtures/issue-1483a.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -388,7 +384,7 @@ def test_issue_1483a_identifier_default(self): def test_issue_1483b_identifier_from_config_file(self): """When the identifier is supplied in the config file only, that identifier is used instead of the default.""" - filename = str(resource_files(__package__).joinpath('fixtures/issue-1483b.conf')) + filename = resources.find(__package__, 'fixtures/issue-1483b.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -407,7 +403,7 @@ def test_issue_1483b_identifier_from_config_file(self): def test_issue_1483c_identifier_from_command_line(self): """When an identifier is supplied in both the config file and on the command line, the one from the command line is used.""" - filename = str(resource_files(__package__).joinpath('fixtures/issue-1483c.conf')) + filename = resources.find(__package__, 'fixtures/issue-1483c.conf') args = ['-m', 'supervisor.supervisord', '-c', filename, '-i', 'from_command_line'] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) From 9deaca10dbcc1abc0fe92108ade124ede35c8b9a Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Wed, 8 Mar 2023 12:01:28 -0800 Subject: [PATCH 073/129] Move importlib shims to compat module, add changelog entry --- CHANGES.rst | 10 ++++++ docs/installing.rst | 7 ++-- supervisor/compat.py | 22 ++++++++++++ supervisor/confecho.py | 6 ++-- supervisor/options.py | 10 ++---- supervisor/resources.py | 40 --------------------- supervisor/tests/test_end_to_end.py | 54 +++++++++++++++++------------ supervisor/tests/test_options.py | 21 ++++++++++- 8 files changed, 91 insertions(+), 79 deletions(-) delete mode 100644 supervisor/resources.py diff --git a/CHANGES.rst b/CHANGES.rst index 41913ef82..d7db33445 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,16 @@ 4.3.0.dev0 (Next Release) ------------------------- +- The installation requirements have changed because Setuptools 67.5.0 + deprecated the use of ``pkg_resources``, which Supervisor used to load + its plugins. The ``setuptools`` package is no longer a runtime dependency + of Supervisor. On Python < 3.8 where ``importlib.metadata`` is not + available in stdlib, Supervisor now requires the PyPI package + ``importlib-metadata``. Additionally, on Python < 3.7 where + ``importlib.resources`` is not available in stdlib, Supervisor now requires + the PyPI package ``importlib-resources``. These new dependencies have been + added as conditional requirements in ``setup.py``. Patch by Ofek Lev. + - ``supervisorctl`` now reads extra files included via the ``[include]`` section in ``supervisord.conf`` like ``supervisord`` does. This allows the ``[supervisorctl]`` section or ``[ctlplugin:x]`` sections to be in diff --git a/docs/installing.rst b/docs/installing.rst index 2e1723a62..f77d34109 100644 --- a/docs/installing.rst +++ b/docs/installing.rst @@ -48,11 +48,8 @@ differently. Since both ``pip`` and ``python setup.py install`` depend on internet access to perform downloads of dependent software, neither will work on machines without internet access until dependencies are installed. To install to a machine which is not -internet-connected, obtain the following dependencies on a machine -which is internet-connected: - -- setuptools (latest) from `https://pypi.org/pypi/setuptools/ - `_. +internet-connected, obtain the dependencies listed in ``setup.py`` +using a machine which is internet-connected. Copy these files to removable media and put them on the target machine. Install each onto the target machine as per its diff --git a/supervisor/compat.py b/supervisor/compat.py index e741f47fb..99c4e226e 100644 --- a/supervisor/compat.py +++ b/supervisor/compat.py @@ -149,3 +149,25 @@ def is_text_stream(stream): from html.parser import HTMLParser except ImportError: # pragma: no cover from HTMLParser import HTMLParser + +try: # pragma: no cover + import importlib.metadata as importlib_metadata +except ImportError: # pragma: no cover + # fall back to PyPI backport if not in stdlib + import importlib_metadata + +try: # pragma: no cover + from importlib import resources as importlib_resources +except ImportError: # pragma: no cover + # fall back to PyPI backport if not in stdlib + import importlib_resources + +if hasattr(importlib_resources, "files"): + def resource_file(package, path): # pragma: no cover + return str(importlib_resources.files(package).joinpath(path)) + +else: # pragma: no cover + # fall back to deprecated .path if .files is not available + def resource_file(package, path): # pragma: no cover + with importlib_resources.path(package, '__init__.py') as p: + return str(p.parent.joinpath(path)) diff --git a/supervisor/confecho.py b/supervisor/confecho.py index 09a40ebce..79f89468e 100644 --- a/supervisor/confecho.py +++ b/supervisor/confecho.py @@ -1,8 +1,8 @@ import sys -from supervisor import resources from supervisor.compat import as_string +from supervisor.compat import resource_file def main(out=sys.stdout): - config = resources.read_text(__package__, 'skel/sample.conf') - out.write(as_string(config)) + with open(resource_file(__package__, 'skel/sample.conf'), 'r') as f: + out.write(as_string(f.read())) diff --git a/supervisor/options.py b/supervisor/options.py index 99a7b6b28..c371e7113 100644 --- a/supervisor/options.py +++ b/supervisor/options.py @@ -21,6 +21,7 @@ from supervisor.compat import xmlrpclib from supervisor.compat import StringIO from supervisor.compat import basestring +from supervisor.compat import importlib_metadata from supervisor.medusa import asyncore_25 as asyncore @@ -54,12 +55,6 @@ from supervisor import xmlrpc from supervisor import poller -if sys.version_info >= (3, 8): - from importlib.metadata import EntryPoint -else: - from importlib_metadata import EntryPoint - - def _read_version_txt(): mydir = os.path.abspath(os.path.dirname(__file__)) version_txt = os.path.join(mydir, 'version.txt') @@ -395,7 +390,8 @@ def get_plugins(self, parser, factory_key, section_prefix): return factories def import_spec(self, spec): - return EntryPoint(None, spec, None).load() + """On failure, raises either AttributeError or ImportError""" + return importlib_metadata.EntryPoint(None, spec, None).load() def read_include_config(self, fp, parser, expansions): if parser.has_section('include'): diff --git a/supervisor/resources.py b/supervisor/resources.py deleted file mode 100644 index ef2a06b6f..000000000 --- a/supervisor/resources.py +++ /dev/null @@ -1,40 +0,0 @@ -import sys - -if sys.version_info >= (3, 9): - from importlib.resources import files - - - def read_text(package, path): - return files(package).joinpath(path).read_text(encoding='utf-8') - - - def find(package, path): - return str(files(package).joinpath(path)) - -elif sys.version_info >= (3, 7): - import importlib.resources - - - def read_text(package, path): - with importlib.resources.path(package, '__init__.py') as p: - return p.parent.joinpath(path).read_text(encoding='utf-8') - - - def find(package, path): - with importlib.resources.path(package, '__init__.py') as p: - return str(p.parent.joinpath(path)) - -else: - from io import open - - import importlib_resources - - - def read_text(package, path): - with open(find(package, path), 'r', encoding='utf-8') as f: - return f.read() - - - def find(package, path): - with importlib_resources.path(package, '__init__.py') as p: - return str(p.parent.joinpath(path)) diff --git a/supervisor/tests/test_end_to_end.py b/supervisor/tests/test_end_to_end.py index 27622e947..88d8e1fa8 100644 --- a/supervisor/tests/test_end_to_end.py +++ b/supervisor/tests/test_end_to_end.py @@ -5,7 +5,7 @@ import signal import sys import unittest -from supervisor import resources +from supervisor.compat import resource_file from supervisor.compat import xmlrpclib from supervisor.xmlrpc import SupervisorTransport @@ -25,7 +25,7 @@ def test_issue_291a_percent_signs_in_original_env_are_preserved(self): passed to the child without the percent sign being mangled.""" key = "SUPERVISOR_TEST_1441B" val = "foo_%s_%_%%_%%%_%2_bar" - filename = resources.find(__package__, 'fixtures/issue-291a.conf') + filename = resource_file(__package__, 'fixtures/issue-291a.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] try: os.environ[key] = val @@ -38,7 +38,7 @@ def test_issue_291a_percent_signs_in_original_env_are_preserved(self): def test_issue_550(self): """When an environment variable is set in the [supervisord] section, it should be put into the environment of the subprocess.""" - filename = resources.find(__package__, 'fixtures/issue-550.conf') + filename = resource_file(__package__, 'fixtures/issue-550.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -54,7 +54,7 @@ def test_issue_550(self): def test_issue_565(self): """When a log file has Unicode characters in it, 'supervisorctl tail -f name' should still work.""" - filename = resources.find(__package__, 'fixtures/issue-565.conf') + filename = resource_file(__package__, 'fixtures/issue-565.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -71,7 +71,7 @@ def test_issue_565(self): def test_issue_638(self): """When a process outputs something on its stdout or stderr file descriptor that is not valid UTF-8, supervisord should not crash.""" - filename = resources.find(__package__, 'fixtures/issue-638.conf') + filename = resource_file(__package__, 'fixtures/issue-638.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -90,7 +90,7 @@ def test_issue_638(self): def test_issue_663(self): """When Supervisor is run on Python 3, the eventlistener protocol should work.""" - filename = resources.find(__package__, 'fixtures/issue-663.conf') + filename = resource_file(__package__, 'fixtures/issue-663.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -102,7 +102,7 @@ def test_issue_664(self): """When a subprocess name has Unicode characters, 'supervisord' should not send incomplete XML-RPC responses and 'supervisorctl status' should work.""" - filename = resources.find(__package__, 'fixtures/issue-664.conf') + filename = resource_file(__package__, 'fixtures/issue-664.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -121,7 +121,7 @@ def test_issue_664(self): def test_issue_733(self): """When a subprocess enters the FATAL state, a one-line eventlistener can be used to signal supervisord to shut down.""" - filename = resources.find(__package__, 'fixtures/issue-733.conf') + filename = resource_file(__package__, 'fixtures/issue-733.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -130,7 +130,7 @@ def test_issue_733(self): supervisord.expect(pexpect.EOF) def test_issue_835(self): - filename = resources.find(__package__, 'fixtures/issue-835.conf') + filename = resource_file(__package__, 'fixtures/issue-835.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -146,7 +146,7 @@ def test_issue_835(self): transport.connection.close() def test_issue_836(self): - filename = resources.find(__package__, 'fixtures/issue-836.conf') + filename = resource_file(__package__, 'fixtures/issue-836.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -169,7 +169,7 @@ def test_issue_836(self): def test_issue_986_command_string_with_double_percent(self): """A percent sign can be used in a command= string without being expanded if it is escaped by a second percent sign.""" - filename = resources.find(__package__, 'fixtures/issue-986.conf') + filename = resource_file(__package__, 'fixtures/issue-986.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -178,7 +178,7 @@ def test_issue_986_command_string_with_double_percent(self): def test_issue_1054(self): """When run on Python 3, the 'supervisorctl avail' command should work.""" - filename = resources.find(__package__, 'fixtures/issue-1054.conf') + filename = resource_file(__package__, 'fixtures/issue-1054.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -196,7 +196,7 @@ def test_issue_1170a(self): """When the [supervisord] section has a variable defined in environment=, that variable should be able to be used in an %(ENV_x) expansion in a [program] section.""" - filename = resources.find(__package__, 'fixtures/issue-1170a.conf') + filename = resource_file(__package__, 'fixtures/issue-1170a.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -207,7 +207,7 @@ def test_issue_1170b(self): environment=, and a variable by the same name is defined in enviroment= of a [program] section, the one in the [program] section should be used.""" - filename = resources.find(__package__, 'fixtures/issue-1170b.conf') + filename = resource_file(__package__, 'fixtures/issue-1170b.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -218,7 +218,7 @@ def test_issue_1170c(self): environment=, and a variable by the same name is defined in enviroment= of an [eventlistener] section, the one in the [eventlistener] section should be used.""" - filename = resources.find(__package__, 'fixtures/issue-1170c.conf') + filename = resource_file(__package__, 'fixtures/issue-1170c.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -229,7 +229,7 @@ def test_issue_1224(self): then the non-rotating logger will be used to avoid an IllegalSeekError in the case that the user has configured a non-seekable file like /dev/stdout.""" - filename = resources.find(__package__, 'fixtures/issue-1224.conf') + filename = resource_file(__package__, 'fixtures/issue-1224.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -238,7 +238,7 @@ def test_issue_1224(self): def test_issue_1231a(self): """When 'supervisorctl tail -f name' is run and the log contains unicode, it should not fail.""" - filename = resources.find(__package__, 'fixtures/issue-1231a.conf') + filename = resource_file(__package__, 'fixtures/issue-1231a.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -255,7 +255,7 @@ def test_issue_1231a(self): def test_issue_1231b(self): """When 'supervisorctl tail -f name' is run and the log contains unicode, it should not fail.""" - filename = resources.find(__package__, 'fixtures/issue-1231b.conf') + filename = resource_file(__package__, 'fixtures/issue-1231b.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -289,7 +289,7 @@ def test_issue_1231b(self): def test_issue_1231c(self): """When 'supervisorctl tail -f name' is run and the log contains unicode, it should not fail.""" - filename = resources.find(__package__, 'fixtures/issue-1231c.conf') + filename = resource_file(__package__, 'fixtures/issue-1231c.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -331,7 +331,7 @@ def test_issue_1298(self): """When the output of 'supervisorctl tail -f worker' is piped such as 'supervisor tail -f worker | grep something', 'supervisorctl' should not crash.""" - filename = resources.find(__package__, 'fixtures/issue-1298.conf') + filename = resource_file(__package__, 'fixtures/issue-1298.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -365,7 +365,7 @@ def test_issue_1418_pidproxy_cmd_with_args(self): def test_issue_1483a_identifier_default(self): """When no identifier is supplied on the command line or in the config file, the default is used.""" - filename = resources.find(__package__, 'fixtures/issue-1483a.conf') + filename = resource_file(__package__, 'fixtures/issue-1483a.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -384,7 +384,7 @@ def test_issue_1483a_identifier_default(self): def test_issue_1483b_identifier_from_config_file(self): """When the identifier is supplied in the config file only, that identifier is used instead of the default.""" - filename = resources.find(__package__, 'fixtures/issue-1483b.conf') + filename = resource_file(__package__, 'fixtures/issue-1483b.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -403,7 +403,7 @@ def test_issue_1483b_identifier_from_config_file(self): def test_issue_1483c_identifier_from_command_line(self): """When an identifier is supplied in both the config file and on the command line, the one from the command line is used.""" - filename = resources.find(__package__, 'fixtures/issue-1483c.conf') + filename = resource_file(__package__, 'fixtures/issue-1483c.conf') args = ['-m', 'supervisor.supervisord', '-c', filename, '-i', 'from_command_line'] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -418,3 +418,11 @@ def test_issue_1483c_identifier_from_command_line(self): finally: transport.close() self.assertEqual(ident, "from_command_line") + + def test_pull_request_1578_echo_supervisord_conf(self): + """The command echo_supervisord_conf, whose implementation depends on + importlib.resources to work, should print the example config.""" + args = ['-c', 'from supervisor import confecho; confecho.main()'] + echo_supervisord_conf = pexpect.spawn(sys.executable, args, encoding='utf-8') + self.addCleanup(echo_supervisord_conf.kill, signal.SIGKILL) + echo_supervisord_conf.expect_exact('Sample supervisor config file') diff --git a/supervisor/tests/test_options.py b/supervisor/tests/test_options.py index 4e7581478..4f3ff71de 100644 --- a/supervisor/tests/test_options.py +++ b/supervisor/tests/test_options.py @@ -2660,7 +2660,26 @@ def test_event_listener_pool_with_event_result_handler(self): gconfig1 = gconfigs[0] self.assertEqual(gconfig1.result_handler, dummy_handler) - def test_event_listener_pool_result_handler_unimportable(self): + def test_event_listener_pool_result_handler_unimportable_ImportError(self): + text = lstrip("""\ + [eventlistener:cat] + events=PROCESS_COMMUNICATION + command = /bin/cat + result_handler = thisishopefullynotanimportablepackage:nonexistent + """) + from supervisor.options import UnhosedConfigParser + config = UnhosedConfigParser() + config.read_string(text) + instance = self._makeOne() + try: + instance.process_groups_from_parser(config) + self.fail('nothing raised') + except ValueError as exc: + self.assertEqual(exc.args[0], + 'thisishopefullynotanimportablepackage:nonexistent cannot be ' + 'resolved within [eventlistener:cat]') + + def test_event_listener_pool_result_handler_unimportable_AttributeError(self): text = lstrip("""\ [eventlistener:cat] events=PROCESS_COMMUNICATION From 25dd835e5a37ee51c9dff65aa468b8a3ec95f5e6 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Wed, 8 Mar 2023 16:13:17 -0800 Subject: [PATCH 074/129] Use consistent import style --- supervisor/compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/supervisor/compat.py b/supervisor/compat.py index 99c4e226e..40725de0c 100644 --- a/supervisor/compat.py +++ b/supervisor/compat.py @@ -157,7 +157,7 @@ def is_text_stream(stream): import importlib_metadata try: # pragma: no cover - from importlib import resources as importlib_resources + import importlib.resources as importlib_resources except ImportError: # pragma: no cover # fall back to PyPI backport if not in stdlib import importlib_resources From 05d8fb2c2f0ed72dbe53c4f406b24706fec75936 Mon Sep 17 00:00:00 2001 From: HieuTVGCH200824 <95976137+HieuTVGCH200824@users.noreply.github.com> Date: Tue, 14 Mar 2023 11:55:48 +0700 Subject: [PATCH 075/129] Update plugins.rst --- docs/plugins.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/plugins.rst b/docs/plugins.rst index 0b04700bf..076d19ccf 100644 --- a/docs/plugins.rst +++ b/docs/plugins.rst @@ -49,6 +49,11 @@ instances running on different servers. Web-based dashboard and command line tool written in Python using PostgreSQL with a REST API, event monitoring, and configuration management. +`Polyvisor `_ + Web-based dashboard written in Python using `flask ` web server. + Frontend based on `Svelte ` result in lightweighted packages. Communicate via supervisor's event-listener. + Providing system resource management via visualized charts & easy to config processes configs via web interface. + Third Party Plugins and Libraries for Supervisor ------------------------------------------------ From 6e618a23a5576ac8c03cf0075f377798cf9678ae Mon Sep 17 00:00:00 2001 From: HieuTVGCH200824 <95976137+HieuTVGCH200824@users.noreply.github.com> Date: Tue, 14 Mar 2023 11:57:01 +0700 Subject: [PATCH 076/129] Update plugins.rst --- docs/plugins.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/plugins.rst b/docs/plugins.rst index 076d19ccf..3618d4949 100644 --- a/docs/plugins.rst +++ b/docs/plugins.rst @@ -50,8 +50,8 @@ instances running on different servers. with a REST API, event monitoring, and configuration management. `Polyvisor `_ - Web-based dashboard written in Python using `flask ` web server. - Frontend based on `Svelte ` result in lightweighted packages. Communicate via supervisor's event-listener. + Web-based dashboard written in Python using `flask `_ web server. + Frontend based on `Svelte `_ result in lightweighted packages. Communicate via supervisor's event-listener. Providing system resource management via visualized charts & easy to config processes configs via web interface. Third Party Plugins and Libraries for Supervisor From 9ed80696da8e02ba76a7be112201593ba85d19a0 Mon Sep 17 00:00:00 2001 From: "winters.zc" Date: Tue, 14 Mar 2023 15:25:40 +0800 Subject: [PATCH 077/129] Fix high cpu usage caused by fd leak We found a problem of high CPU usage of the supervisor. This problem is caused by continuous polling of a wrong fd in the main loop of the supervisor. Busy polling leads to a CPU usage close to 100%. (We can confirm this problem through the strace tool) This issue can be reproduced by: 1. Continuously initiate arbitrary requests to supervisor through supervisorctl 2. After the socket fd is closed, trigger the supervisor's subprocess to rotate the log (or reopen the file) 3. If the above steps are completed within a single main loop of the supervisor, the problem can be triggered The reason for the problem is that supervisor relies on using _ignore_invalid() in the main loop to close fds. This method has a flaw that if fd is reused before _ignore_invalid() is called, then the fd may always exist in the fd list of poll . This commit fixes the problem. By checking the validity of the fd in the event list in the main loop, if the fd is not in the combined_map, it is considered to be an invalid fd and will be removed from the list. --- supervisor/supervisord.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/supervisor/supervisord.py b/supervisor/supervisord.py index 0a4f3e697..2265db9c7 100755 --- a/supervisor/supervisord.py +++ b/supervisor/supervisord.py @@ -222,6 +222,14 @@ def runforever(self): raise except: combined_map[fd].handle_error() + else: + # if the fd is not in combined_map, we should unregister it. otherwise, + # it will be polled every time, which may cause 100% cpu usage + self.options.logger.warn('unexpected read event from fd %r' % fd) + try: + self.options.poller.unregister_readable(fd) + except: + pass for fd in w: if fd in combined_map: @@ -237,6 +245,12 @@ def runforever(self): raise except: combined_map[fd].handle_error() + else: + self.options.logger.warn('unexpected write event from fd %r' % fd) + try: + self.options.poller.unregister_writable(fd) + except: + pass for group in pgroups: group.transition() From f12bd030589d2f757886b03bd0f1b6e59ffb3f05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Blondon?= Date: Sat, 18 Mar 2023 17:45:30 +0100 Subject: [PATCH 078/129] Remove python3.3 case Miminal current python3 requirement is 3.4 so this case can't occur. --- setup.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/setup.py b/setup.py index 8ef9f7bb7..252d8038d 100644 --- a/setup.py +++ b/setup.py @@ -27,8 +27,6 @@ "importlib-resources; python_version < '3.7'", ] tests_require = [] -if py_version < (3, 3): - tests_require.append('mock<4.0.0.dev0') testing_extras = tests_require + [ 'pytest', From 19c68f5d8832315cf91b5e45cd4e412295d87ead Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Thu, 23 Mar 2023 15:10:13 -0700 Subject: [PATCH 079/129] Avoid new dependencies that will likely break installs See this explanation: https://github.com/Supervisor/supervisor/pull/1578#issuecomment-1477046229 --- CHANGES.rst | 11 +----- setup.py | 7 ++-- supervisor/compat.py | 57 +++++++++++++++++++++-------- supervisor/confecho.py | 4 +- supervisor/options.py | 4 +- supervisor/tests/test_end_to_end.py | 46 +++++++++++------------ 6 files changed, 75 insertions(+), 54 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index d7db33445..27323b678 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,15 +1,8 @@ 4.3.0.dev0 (Next Release) ------------------------- -- The installation requirements have changed because Setuptools 67.5.0 - deprecated the use of ``pkg_resources``, which Supervisor used to load - its plugins. The ``setuptools`` package is no longer a runtime dependency - of Supervisor. On Python < 3.8 where ``importlib.metadata`` is not - available in stdlib, Supervisor now requires the PyPI package - ``importlib-metadata``. Additionally, on Python < 3.7 where - ``importlib.resources`` is not available in stdlib, Supervisor now requires - the PyPI package ``importlib-resources``. These new dependencies have been - added as conditional requirements in ``setup.py``. Patch by Ofek Lev. +- On Python 3.8 and later, ``setuptools`` is no longer a runtime + dependency. Patch by Ofek Lev. - ``supervisorctl`` now reads extra files included via the ``[include]`` section in ``supervisord.conf`` like ``supervisord`` does. This allows diff --git a/setup.py b/setup.py index 252d8038d..eeb9b34e3 100644 --- a/setup.py +++ b/setup.py @@ -22,12 +22,13 @@ elif (3, 0) < py_version < (3, 4): raise RuntimeError('On Python 3, Supervisor requires Python 3.4 or later') +# setuptools is required as a runtime dependency only on +# Python < 3.8. See the comments in supervisor/compat.py. requires = [ - "importlib-metadata; python_version < '3.8'", - "importlib-resources; python_version < '3.7'", + "setuptools; python_version < '3.8'", ] -tests_require = [] +tests_require = [] testing_extras = tests_require + [ 'pytest', 'pytest-cov', diff --git a/supervisor/compat.py b/supervisor/compat.py index 40725de0c..023223fbd 100644 --- a/supervisor/compat.py +++ b/supervisor/compat.py @@ -150,24 +150,51 @@ def is_text_stream(stream): except ImportError: # pragma: no cover from HTMLParser import HTMLParser +# Begin importlib/setuptools compatibility code + +# Supervisor used pkg_resources (a part of setuptools) to load package +# resources for 15 years, until setuptools 67.5.0 (2023-03-05) deprecated +# the use of pkg_resources. On Python 3.8 or later, Supervisor now uses +# importlib (part of Python 3 stdlib). Unfortunately, on Python < 3.8, +# Supervisor needs to use pkg_resources despite its deprecation. The PyPI +# backport packages "importlib-resources" and "importlib-metadata" couldn't +# be added as dependencies to Supervisor because they require even more +# dependencies that would likely cause some Supervisor installs to fail. +from warnings import filterwarnings as _fw +_fw("ignore", message="pkg_resources is deprecated as an API") + try: # pragma: no cover - import importlib.metadata as importlib_metadata + from importlib.metadata import EntryPoint as _EntryPoint + + def import_spec(spec): + return _EntryPoint(None, spec, None).load() + except ImportError: # pragma: no cover - # fall back to PyPI backport if not in stdlib - import importlib_metadata + from pkg_resources import EntryPoint as _EntryPoint + + def import_spec(spec): + ep = _EntryPoint.parse("x=" + spec) + if hasattr(ep, 'resolve'): + # this is available on setuptools >= 10.2 + return ep.resolve() + else: + # this causes a DeprecationWarning on setuptools >= 11.3 + return ep.load(False) try: # pragma: no cover - import importlib.resources as importlib_resources -except ImportError: # pragma: no cover - # fall back to PyPI backport if not in stdlib - import importlib_resources + import importlib.resources as _importlib_resources -if hasattr(importlib_resources, "files"): - def resource_file(package, path): # pragma: no cover - return str(importlib_resources.files(package).joinpath(path)) + if hasattr(_importlib_resources, "files"): + def resource_filename(package, path): + return str(_importlib_resources.files(package).joinpath(path)) -else: # pragma: no cover - # fall back to deprecated .path if .files is not available - def resource_file(package, path): # pragma: no cover - with importlib_resources.path(package, '__init__.py') as p: - return str(p.parent.joinpath(path)) + else: + # fall back to deprecated .path if .files is not available + def resource_filename(package, path): + with _importlib_resources.path(package, '__init__.py') as p: + return str(p.parent.joinpath(path)) + +except ImportError: # pragma: no cover + from pkg_resources import resource_filename + +# End importlib/setuptools compatibility code diff --git a/supervisor/confecho.py b/supervisor/confecho.py index 79f89468e..a8655d98d 100644 --- a/supervisor/confecho.py +++ b/supervisor/confecho.py @@ -1,8 +1,8 @@ import sys from supervisor.compat import as_string -from supervisor.compat import resource_file +from supervisor.compat import resource_filename def main(out=sys.stdout): - with open(resource_file(__package__, 'skel/sample.conf'), 'r') as f: + with open(resource_filename(__package__, 'skel/sample.conf'), 'r') as f: out.write(as_string(f.read())) diff --git a/supervisor/options.py b/supervisor/options.py index c371e7113..271735200 100644 --- a/supervisor/options.py +++ b/supervisor/options.py @@ -21,7 +21,7 @@ from supervisor.compat import xmlrpclib from supervisor.compat import StringIO from supervisor.compat import basestring -from supervisor.compat import importlib_metadata +from supervisor.compat import import_spec from supervisor.medusa import asyncore_25 as asyncore @@ -391,7 +391,7 @@ def get_plugins(self, parser, factory_key, section_prefix): def import_spec(self, spec): """On failure, raises either AttributeError or ImportError""" - return importlib_metadata.EntryPoint(None, spec, None).load() + return import_spec(spec) def read_include_config(self, fp, parser, expansions): if parser.has_section('include'): diff --git a/supervisor/tests/test_end_to_end.py b/supervisor/tests/test_end_to_end.py index 88d8e1fa8..bc7c47421 100644 --- a/supervisor/tests/test_end_to_end.py +++ b/supervisor/tests/test_end_to_end.py @@ -5,7 +5,7 @@ import signal import sys import unittest -from supervisor.compat import resource_file +from supervisor.compat import resource_filename from supervisor.compat import xmlrpclib from supervisor.xmlrpc import SupervisorTransport @@ -25,7 +25,7 @@ def test_issue_291a_percent_signs_in_original_env_are_preserved(self): passed to the child without the percent sign being mangled.""" key = "SUPERVISOR_TEST_1441B" val = "foo_%s_%_%%_%%%_%2_bar" - filename = resource_file(__package__, 'fixtures/issue-291a.conf') + filename = resource_filename(__package__, 'fixtures/issue-291a.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] try: os.environ[key] = val @@ -38,7 +38,7 @@ def test_issue_291a_percent_signs_in_original_env_are_preserved(self): def test_issue_550(self): """When an environment variable is set in the [supervisord] section, it should be put into the environment of the subprocess.""" - filename = resource_file(__package__, 'fixtures/issue-550.conf') + filename = resource_filename(__package__, 'fixtures/issue-550.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -54,7 +54,7 @@ def test_issue_550(self): def test_issue_565(self): """When a log file has Unicode characters in it, 'supervisorctl tail -f name' should still work.""" - filename = resource_file(__package__, 'fixtures/issue-565.conf') + filename = resource_filename(__package__, 'fixtures/issue-565.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -71,7 +71,7 @@ def test_issue_565(self): def test_issue_638(self): """When a process outputs something on its stdout or stderr file descriptor that is not valid UTF-8, supervisord should not crash.""" - filename = resource_file(__package__, 'fixtures/issue-638.conf') + filename = resource_filename(__package__, 'fixtures/issue-638.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -90,7 +90,7 @@ def test_issue_638(self): def test_issue_663(self): """When Supervisor is run on Python 3, the eventlistener protocol should work.""" - filename = resource_file(__package__, 'fixtures/issue-663.conf') + filename = resource_filename(__package__, 'fixtures/issue-663.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -102,7 +102,7 @@ def test_issue_664(self): """When a subprocess name has Unicode characters, 'supervisord' should not send incomplete XML-RPC responses and 'supervisorctl status' should work.""" - filename = resource_file(__package__, 'fixtures/issue-664.conf') + filename = resource_filename(__package__, 'fixtures/issue-664.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -121,7 +121,7 @@ def test_issue_664(self): def test_issue_733(self): """When a subprocess enters the FATAL state, a one-line eventlistener can be used to signal supervisord to shut down.""" - filename = resource_file(__package__, 'fixtures/issue-733.conf') + filename = resource_filename(__package__, 'fixtures/issue-733.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -130,7 +130,7 @@ def test_issue_733(self): supervisord.expect(pexpect.EOF) def test_issue_835(self): - filename = resource_file(__package__, 'fixtures/issue-835.conf') + filename = resource_filename(__package__, 'fixtures/issue-835.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -146,7 +146,7 @@ def test_issue_835(self): transport.connection.close() def test_issue_836(self): - filename = resource_file(__package__, 'fixtures/issue-836.conf') + filename = resource_filename(__package__, 'fixtures/issue-836.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -169,7 +169,7 @@ def test_issue_836(self): def test_issue_986_command_string_with_double_percent(self): """A percent sign can be used in a command= string without being expanded if it is escaped by a second percent sign.""" - filename = resource_file(__package__, 'fixtures/issue-986.conf') + filename = resource_filename(__package__, 'fixtures/issue-986.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -178,7 +178,7 @@ def test_issue_986_command_string_with_double_percent(self): def test_issue_1054(self): """When run on Python 3, the 'supervisorctl avail' command should work.""" - filename = resource_file(__package__, 'fixtures/issue-1054.conf') + filename = resource_filename(__package__, 'fixtures/issue-1054.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -196,7 +196,7 @@ def test_issue_1170a(self): """When the [supervisord] section has a variable defined in environment=, that variable should be able to be used in an %(ENV_x) expansion in a [program] section.""" - filename = resource_file(__package__, 'fixtures/issue-1170a.conf') + filename = resource_filename(__package__, 'fixtures/issue-1170a.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -207,7 +207,7 @@ def test_issue_1170b(self): environment=, and a variable by the same name is defined in enviroment= of a [program] section, the one in the [program] section should be used.""" - filename = resource_file(__package__, 'fixtures/issue-1170b.conf') + filename = resource_filename(__package__, 'fixtures/issue-1170b.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -218,7 +218,7 @@ def test_issue_1170c(self): environment=, and a variable by the same name is defined in enviroment= of an [eventlistener] section, the one in the [eventlistener] section should be used.""" - filename = resource_file(__package__, 'fixtures/issue-1170c.conf') + filename = resource_filename(__package__, 'fixtures/issue-1170c.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -229,7 +229,7 @@ def test_issue_1224(self): then the non-rotating logger will be used to avoid an IllegalSeekError in the case that the user has configured a non-seekable file like /dev/stdout.""" - filename = resource_file(__package__, 'fixtures/issue-1224.conf') + filename = resource_filename(__package__, 'fixtures/issue-1224.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -238,7 +238,7 @@ def test_issue_1224(self): def test_issue_1231a(self): """When 'supervisorctl tail -f name' is run and the log contains unicode, it should not fail.""" - filename = resource_file(__package__, 'fixtures/issue-1231a.conf') + filename = resource_filename(__package__, 'fixtures/issue-1231a.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -255,7 +255,7 @@ def test_issue_1231a(self): def test_issue_1231b(self): """When 'supervisorctl tail -f name' is run and the log contains unicode, it should not fail.""" - filename = resource_file(__package__, 'fixtures/issue-1231b.conf') + filename = resource_filename(__package__, 'fixtures/issue-1231b.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -289,7 +289,7 @@ def test_issue_1231b(self): def test_issue_1231c(self): """When 'supervisorctl tail -f name' is run and the log contains unicode, it should not fail.""" - filename = resource_file(__package__, 'fixtures/issue-1231c.conf') + filename = resource_filename(__package__, 'fixtures/issue-1231c.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -331,7 +331,7 @@ def test_issue_1298(self): """When the output of 'supervisorctl tail -f worker' is piped such as 'supervisor tail -f worker | grep something', 'supervisorctl' should not crash.""" - filename = resource_file(__package__, 'fixtures/issue-1298.conf') + filename = resource_filename(__package__, 'fixtures/issue-1298.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -365,7 +365,7 @@ def test_issue_1418_pidproxy_cmd_with_args(self): def test_issue_1483a_identifier_default(self): """When no identifier is supplied on the command line or in the config file, the default is used.""" - filename = resource_file(__package__, 'fixtures/issue-1483a.conf') + filename = resource_filename(__package__, 'fixtures/issue-1483a.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -384,7 +384,7 @@ def test_issue_1483a_identifier_default(self): def test_issue_1483b_identifier_from_config_file(self): """When the identifier is supplied in the config file only, that identifier is used instead of the default.""" - filename = resource_file(__package__, 'fixtures/issue-1483b.conf') + filename = resource_filename(__package__, 'fixtures/issue-1483b.conf') args = ['-m', 'supervisor.supervisord', '-c', filename] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) @@ -403,7 +403,7 @@ def test_issue_1483b_identifier_from_config_file(self): def test_issue_1483c_identifier_from_command_line(self): """When an identifier is supplied in both the config file and on the command line, the one from the command line is used.""" - filename = resource_file(__package__, 'fixtures/issue-1483c.conf') + filename = resource_filename(__package__, 'fixtures/issue-1483c.conf') args = ['-m', 'supervisor.supervisord', '-c', filename, '-i', 'from_command_line'] supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(supervisord.kill, signal.SIGINT) From 76f4420b643ba998de71486ff666cd521f4e2db5 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Sat, 25 Mar 2023 12:41:57 -0700 Subject: [PATCH 080/129] Remove unused alias easy_install was deprecated in setuptools 42.0.0 --- setup.cfg | 3 --- 1 file changed, 3 deletions(-) diff --git a/setup.cfg b/setup.cfg index a980de220..e2b933446 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,8 +1,5 @@ [easy_install] zip_ok = false -[aliases] -dev = develop easy_install supervisor[testing] - [bdist_wheel] universal = 1 From 01546d35354113cea4e2714f50c2a2b910fd7278 Mon Sep 17 00:00:00 2001 From: Rick Schubert Date: Mon, 27 Mar 2023 16:30:33 +0100 Subject: [PATCH 081/129] Remove unnecessarily gendered pronoun when talking about a computer program --- docs/introduction.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/introduction.rst b/docs/introduction.rst index bb63b4d25..ccd1c0c45 100644 --- a/docs/introduction.rst +++ b/docs/introduction.rst @@ -134,7 +134,7 @@ Supervisor Components The command-line client talks to the server across a UNIX domain socket or an internet (TCP) socket. The server can assert that the user of a client should present authentication credentials before it - allows him to perform commands. The client process typically uses + allows them to perform commands. The client process typically uses the same configuration file as the server but any configuration file with a ``[supervisorctl]`` section in it will work. From b9819c385e089a9762e9ae623973a708136775fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Sch=C3=BC=C3=9Fler?= <834347+vindolin@users.noreply.github.com> Date: Mon, 10 Apr 2023 09:38:37 +0200 Subject: [PATCH 082/129] rdflib.net is now rdflib.dev rdflib.net is now owned by a domain squatter that relays to a gambling site. --- supervisor/http_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/supervisor/http_client.py b/supervisor/http_client.py index 1ce2275a1..1c1ff756d 100644 --- a/supervisor/http_client.py +++ b/supervisor/http_client.py @@ -1,4 +1,4 @@ -# this code based on Daniel Krech's RDFLib HTTP client code (see rdflib.net) +# this code based on Daniel Krech's RDFLib HTTP client code (see rdflib.dev) import sys import socket From 8c81ee17a3909b172984bc16b574677290573458 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Sun, 7 May 2023 19:16:35 -0700 Subject: [PATCH 083/129] Remove Ubuntu 18.04 env no longer supported by GitHub --- .github/workflows/main.yml | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 16ad87ca9..c5f7e5edd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -31,34 +31,6 @@ jobs: - name: Run the end-to-end tests run: TOXENV=${{steps.toxenv.outputs.TOXENV}} END_TO_END=1 tox - tests_ubuntu_1804: - runs-on: ubuntu-18.04 - strategy: - fail-fast: false - matrix: - python-version: [3.4] # not supported by setup-python on ubuntu 20.04 - - steps: - - uses: actions/checkout@v3 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - - name: Install dependencies - run: pip install virtualenv tox - - - name: Set variable for TOXENV based on Python version - id: toxenv - run: python -c 'import sys; print("TOXENV=py%d%d" % (sys.version_info.major, sys.version_info.minor))' | tee -a $GITHUB_OUTPUT - - - name: Run the unit tests - run: TOXENV=${{steps.toxenv.outputs.TOXENV}} tox - - - name: Run the end-to-end tests - run: TOXENV=${{steps.toxenv.outputs.TOXENV}} END_TO_END=1 tox - coverage: runs-on: ubuntu-20.04 strategy: From 49792c94314c9b261934bc406d7e7a89c21c124b Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Wed, 14 Jun 2023 08:11:05 -0700 Subject: [PATCH 084/129] Add config file now required by Read the Docs https://blog.readthedocs.com/migrate-configuration-v2/ --- .readthedocs.yaml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .readthedocs.yaml diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 000000000..4a775cbd3 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,23 @@ +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the version of Python and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.11" + +# Build documentation in the docs/ directory with Sphinx +sphinx: + configuration: docs/conf.py + +# We recommend specifying your dependencies to enable reproducible builds: +# https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html +# python: +# install: +# - requirements: docs/requirements.txt + From c1ce3d619b0c1308fd0739d96bd6eda67192746e Mon Sep 17 00:00:00 2001 From: Julien LE CLEACH Date: Fri, 28 Jul 2023 18:48:32 +0200 Subject: [PATCH 085/129] closes #1596 --- supervisor/medusa/asyncore_25.py | 1 + 1 file changed, 1 insertion(+) diff --git a/supervisor/medusa/asyncore_25.py b/supervisor/medusa/asyncore_25.py index a0fb8d95f..1c578310a 100644 --- a/supervisor/medusa/asyncore_25.py +++ b/supervisor/medusa/asyncore_25.py @@ -358,6 +358,7 @@ def recv(self, buffer_size): def close(self): self.del_channel() + self.socket.shutdown(socket.SHUT_RDWR) self.socket.close() # cheap inheritance, used to pass all other attribute From 9325eddc8f483d86d426a0c2b9c0a318c21623fa Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Wed, 9 Aug 2023 10:26:59 -0700 Subject: [PATCH 086/129] Fix typo in API docs --- docs/api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api.rst b/docs/api.rst index e3ef91147..aadb26605 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -297,7 +297,7 @@ Process Control .. describe:: stderr_logfile - Absolute path and filename to the STDOUT logfile + Absolute path and filename to the STDERR logfile .. describe:: spawnerr From 50cdd82a3f2dd3d04452a3d41fb60771fd931c42 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Tue, 19 Sep 2023 12:06:56 -0700 Subject: [PATCH 087/129] Fix Python 2.7 and 3.4 tests on CI --- .github/workflows/main.yml | 65 ++++++++++++++++++++++++++++++++------ 1 file changed, 56 insertions(+), 9 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c5f7e5edd..47f35d613 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -3,12 +3,48 @@ name: Run all tests on: [push, pull_request] jobs: - tests: + tests_py27: + runs-on: ubuntu-20.04 + container: python:2.7 + strategy: + fail-fast: false + + steps: + - uses: actions/checkout@v3 + + - name: Install dependencies + run: pip install virtualenv tox + + - name: Run the unit tests + run: TOXENV=py27 tox + + - name: Run the end-to-end tests + run: TOXENV=py27 END_TO_END=1 tox + + tests_py34: + runs-on: ubuntu-20.04 + container: python:3.4 + strategy: + fail-fast: false + + steps: + - uses: actions/checkout@v3 + + - name: Install dependencies + run: pip install virtualenv tox + + - name: Run the unit tests + run: TOXENV=py34 tox + + - name: Run the end-to-end tests + run: TOXENV=py34 END_TO_END=1 tox + + tests_py3x: runs-on: ubuntu-20.04 strategy: fail-fast: false matrix: - python-version: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9, "3.10", 3.11] + python-version: [3.5, 3.6, 3.7, 3.8, 3.9, "3.10", 3.11] steps: - uses: actions/checkout@v3 @@ -31,12 +67,27 @@ jobs: - name: Run the end-to-end tests run: TOXENV=${{steps.toxenv.outputs.TOXENV}} END_TO_END=1 tox - coverage: + coverage_py27: + runs-on: ubuntu-20.04 + container: python:2.7 + strategy: + fail-fast: false + + steps: + - uses: actions/checkout@v3 + + - name: Install dependencies + run: pip install virtualenv tox + + - name: Run unit test coverage + run: TOXENV=cover tox + + coverage_py3x: runs-on: ubuntu-20.04 strategy: fail-fast: false matrix: - python-version: [2.7, 3.8] + python-version: [3.8] steps: - uses: actions/checkout@v3 @@ -49,12 +100,8 @@ jobs: - name: Install dependencies run: pip install virtualenv tox - - name: Set variable for TOXENV based on Python version - id: toxenv - run: python -c 'import sys; e="cover" if sys.version_info.major == 2 else "cover3"; print("TOXENV=%s" % e)' | tee -a $GITHUB_OUTPUT - - name: Run unit test coverage - run: TOXENV=${{steps.toxenv.outputs.TOXENV}} tox + run: TOXENV=cover3 tox docs: runs-on: ubuntu-20.04 From b8d00ab336f4dc0b3f1cd7bcb1b351e342017be7 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Tue, 19 Sep 2023 12:52:24 -0700 Subject: [PATCH 088/129] Avoid pip version warnings on CI Annotations 1 error tests_py3x (3.5) You are using pip version 20.3.4, however version 23.2.1 is available. You should consider upgrading via the 'pip install --upgrade pip' command. --- .github/workflows/main.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 47f35d613..d191b4315 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -2,6 +2,9 @@ name: Run all tests on: [push, pull_request] +env: + PIP_DISABLE_PIP_VERSION_CHECK: 1 + jobs: tests_py27: runs-on: ubuntu-20.04 From 2171072ee62680a5e7be810bec4b6790dc8ce72d Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Fri, 6 Oct 2023 09:36:49 -0700 Subject: [PATCH 089/129] Add Python 3.12 --- .github/workflows/main.yml | 2 +- setup.py | 1 + tox.ini | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d191b4315..9507d7501 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -47,7 +47,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.5, 3.6, 3.7, 3.8, 3.9, "3.10", 3.11] + python-version: [3.5, 3.6, 3.7, 3.8, 3.9, "3.10", 3.11, 3.12] steps: - uses: actions/checkout@v3 diff --git a/setup.py b/setup.py index eeb9b34e3..380dfa960 100644 --- a/setup.py +++ b/setup.py @@ -68,6 +68,7 @@ "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", ] version_txt = os.path.join(here, 'supervisor/version.txt') diff --git a/tox.ini b/tox.ini index b495fb11d..9b3cb232f 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] envlist = - cover,cover3,docs,py27,py34,py35,py36,py37,py38,py39,py310 + cover,cover3,docs,py27,py34,py35,py36,py37,py38,py39,py310,py311,py312 [testenv] deps = From ff5356f65ebc557554bbe1b39a1f320ab346d064 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Sat, 18 Nov 2023 14:08:03 -0800 Subject: [PATCH 090/129] Show less noise from pip on CI --- .github/workflows/main.yml | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9507d7501..586abfbad 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -3,7 +3,9 @@ name: Run all tests on: [push, pull_request] env: - PIP_DISABLE_PIP_VERSION_CHECK: 1 + PIP: "env PIP_DISABLE_PIP_VERSION_CHECK=1 + PYTHONWARNINGS=ignore:DEPRECATION + pip --no-cache-dir" jobs: tests_py27: @@ -16,7 +18,7 @@ jobs: - uses: actions/checkout@v3 - name: Install dependencies - run: pip install virtualenv tox + run: $PIP install virtualenv tox - name: Run the unit tests run: TOXENV=py27 tox @@ -34,7 +36,7 @@ jobs: - uses: actions/checkout@v3 - name: Install dependencies - run: pip install virtualenv tox + run: $PIP install virtualenv tox - name: Run the unit tests run: TOXENV=py34 tox @@ -58,7 +60,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install dependencies - run: pip install virtualenv tox + run: $PIP install virtualenv tox - name: Set variable for TOXENV based on Python version id: toxenv @@ -80,7 +82,7 @@ jobs: - uses: actions/checkout@v3 - name: Install dependencies - run: pip install virtualenv tox + run: $PIP install virtualenv tox - name: Run unit test coverage run: TOXENV=cover tox @@ -101,7 +103,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install dependencies - run: pip install virtualenv tox + run: $PIP install virtualenv tox - name: Run unit test coverage run: TOXENV=cover3 tox @@ -118,7 +120,7 @@ jobs: python-version: "3.8" - name: Install dependencies - run: pip install virtualenv tox>=4.0.0 + run: $PIP install virtualenv tox>=4.0.0 - name: Build the docs run: TOXENV=docs tox From 6a526b9471241157349f07787d528af8e5ffe0f1 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 12 Jan 2024 14:15:13 +0530 Subject: [PATCH 091/129] chore: typo 1 + 2 + 3 would be total of 6 seconds of wait --- docs/subprocess.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/subprocess.rst b/docs/subprocess.rst index 15ad25273..5a6b46713 100644 --- a/docs/subprocess.rst +++ b/docs/subprocess.rst @@ -263,7 +263,7 @@ exceeded the maximum, at which point it will transition to the So if you set ``startretries=3``, :program:`supervisord` will wait one, two and then three seconds between each restart attempt, for a total of - 5 seconds. + 6 seconds. When a process is in the ``EXITED`` state, it will automatically restart: From 9ee5fee61881adc227a67d2d13414597d1f4d51c Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Sat, 2 Mar 2024 11:34:25 -0800 Subject: [PATCH 092/129] Fix crash closing already-closed socket. Refs #1596 ``` error: uncaptured python exception, closing channel (:[Errno 57] Socket is not connected [/Users/username/git/supervisor/supervisor/medusa/asynchat_25.py|handle_read|89] [/Users/username/git/supervisor/supervisor/medusa/http_server.py|recv|528] [/Users/username/git/supervisor/supervisor/medusa/asyncore_25.py|recv|354] [/Users/username/git/supervisor/supervisor/medusa/asynchat_25.py|handle_close|156] [/Users/username/git/supervisor/supervisor/medusa/asyncore_25.py|close|361]) error: uncaptured python exception, closing channel (:[Errno 57] Socket is not connected [/Users/username/git/supervisor/supervisor/supervisord.py|runforever|218] [/Users/username/git/supervisor/supervisor/medusa/asyncore_25.py|handle_read_event|392] [/Users/username/git/supervisor/supervisor/medusa/asynchat_25.py|handle_read|91] [/Users/username/git/supervisor/supervisor/medusa/http_server.py|handle_error|546] [/Users/username/git/supervisor/supervisor/medusa/asyncore_25.py|handle_error|422] [/Users/username/git/supervisor/supervisor/medusa/asyncore_25.py|close|361]) Traceback (most recent call last): File "/Users/username/git/supervisor/supervisor/medusa/asyncore_25.py", line 347, in recv self.handle_close() File "/Users/username/git/supervisor/supervisor/medusa/asynchat_25.py", line 156, in handle_close self.close() File "/Users/username/git/supervisor/supervisor/medusa/asyncore_25.py", line 361, in close self.socket.shutdown(socket.SHUT_RDWR) OSError: [Errno 57] Socket is not connected During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/Users/username/git/supervisor/supervisor/medusa/asynchat_25.py", line 89, in handle_read data = self.recv (self.ac_in_buffer_size) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/username/git/supervisor/supervisor/medusa/http_server.py", line 528, in recv result = asynchat.async_chat.recv (self, buffer_size) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/username/git/supervisor/supervisor/medusa/asyncore_25.py", line 354, in recv self.handle_close() File "/Users/username/git/supervisor/supervisor/medusa/asynchat_25.py", line 156, in handle_close self.close() File "/Users/username/git/supervisor/supervisor/medusa/asyncore_25.py", line 361, in close self.socket.shutdown(socket.SHUT_RDWR) OSError: [Errno 57] Socket is not connected During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/Users/username/git/supervisor/supervisor/supervisord.py", line 218, in runforever dispatcher.handle_read_event() File "/Users/username/git/supervisor/supervisor/medusa/asyncore_25.py", line 392, in handle_read_event self.handle_read() File "/Users/username/git/supervisor/supervisor/medusa/asynchat_25.py", line 91, in handle_read self.handle_error() File "/Users/username/git/supervisor/supervisor/medusa/http_server.py", line 546, in handle_error asynchat.async_chat.handle_error (self) File "/Users/username/git/supervisor/supervisor/medusa/asyncore_25.py", line 422, in handle_error self.close() File "/Users/username/git/supervisor/supervisor/medusa/asyncore_25.py", line 361, in close self.socket.shutdown(socket.SHUT_RDWR) OSError: [Errno 57] Socket is not connected During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/opt/homebrew/bin/supervisord", line 33, in sys.exit(load_entry_point('supervisor', 'console_scripts', 'supervisord')()) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/username/git/supervisor/supervisor/supervisord.py", line 373, in main go(options) File "/Users/username/git/supervisor/supervisor/supervisord.py", line 383, in go d.main() File "/Users/username/git/supervisor/supervisor/supervisord.py", line 78, in main self.run() File "/Users/username/git/supervisor/supervisor/supervisord.py", line 94, in run self.runforever() File "/Users/username/git/supervisor/supervisor/supervisord.py", line 224, in runforever combined_map[fd].handle_error() File "/Users/username/git/supervisor/supervisor/medusa/http_server.py", line 546, in handle_error asynchat.async_chat.handle_error (self) File "/Users/username/git/supervisor/supervisor/medusa/asyncore_25.py", line 422, in handle_error self.close() File "/Users/username/git/supervisor/supervisor/medusa/asyncore_25.py", line 361, in close self.socket.shutdown(socket.SHUT_RDWR) OSError: [Errno 57] Socket is not connected ``` --- supervisor/medusa/asyncore_25.py | 12 ++++++++-- supervisor/tests/fixtures/issue-1596.conf | 12 ++++++++++ supervisor/tests/test_end_to_end.py | 29 +++++++++++++++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 supervisor/tests/fixtures/issue-1596.conf diff --git a/supervisor/medusa/asyncore_25.py b/supervisor/medusa/asyncore_25.py index 1c578310a..d3efdf7a5 100644 --- a/supervisor/medusa/asyncore_25.py +++ b/supervisor/medusa/asyncore_25.py @@ -358,8 +358,16 @@ def recv(self, buffer_size): def close(self): self.del_channel() - self.socket.shutdown(socket.SHUT_RDWR) - self.socket.close() + + try: + self.socket.shutdown(socket.SHUT_RDWR) + except socket.error: + # must swallow exception from already-closed socket + # (at least with Python 3.11.7 on macOS 14.2.1) + pass + + # does not raise if called on already-closed socket + self.socket.close() # cheap inheritance, used to pass all other attribute # references to the underlying socket object. diff --git a/supervisor/tests/fixtures/issue-1596.conf b/supervisor/tests/fixtures/issue-1596.conf new file mode 100644 index 000000000..750214bbe --- /dev/null +++ b/supervisor/tests/fixtures/issue-1596.conf @@ -0,0 +1,12 @@ +[supervisord] +loglevel=info ; log level; default info; others: debug,warn,trace +logfile=/tmp/issue-1596.log ; main log file; default $CWD/supervisord.log +pidfile=/tmp/issue-1596.pid ; supervisord pidfile; default supervisord.pid +nodaemon=true ; start in foreground if true; default false +identifier=from_config_file + +[rpcinterface:supervisor] +supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface + +[unix_http_server] +file=/tmp/issue-1596.sock ; the path to the socket file diff --git a/supervisor/tests/test_end_to_end.py b/supervisor/tests/test_end_to_end.py index bc7c47421..25b71d2a2 100644 --- a/supervisor/tests/test_end_to_end.py +++ b/supervisor/tests/test_end_to_end.py @@ -426,3 +426,32 @@ def test_pull_request_1578_echo_supervisord_conf(self): echo_supervisord_conf = pexpect.spawn(sys.executable, args, encoding='utf-8') self.addCleanup(echo_supervisord_conf.kill, signal.SIGKILL) echo_supervisord_conf.expect_exact('Sample supervisor config file') + + def test_issue_1596_asyncore_close_does_not_crash(self): + """If the socket is already closed when socket.shutdown(socket.SHUT_RDWR) + is called in the close() method of an asyncore dispatcher, an exception + will be raised (at least with Python 3.11.7 on macOS 14.2.1). If it is + not caught in that method, supervisord will crash.""" + filename = resource_filename(__package__, 'fixtures/issue-1596.conf') + args = ['-m', 'supervisor.supervisord', '-c', filename] + supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8') + self.addCleanup(supervisord.kill, signal.SIGINT) + supervisord.expect_exact('supervisord started with pid') + + from supervisor.compat import xmlrpclib + from supervisor.xmlrpc import SupervisorTransport + + socket_url = 'unix:///tmp/issue-1596.sock' + dummy_url = 'http://transport.ignores.host/RPC2' + + # supervisord will crash after close() if it has the bug + t1 = SupervisorTransport('', '', socket_url) + s1 = xmlrpclib.ServerProxy(dummy_url, t1) + s1.system.listMethods() + t1.close() + + # this call will only succeed if supervisord did not crash + t2 = SupervisorTransport('', '', socket_url) + s2 = xmlrpclib.ServerProxy(dummy_url, t2) + s2.system.listMethods() + t2.close() From 69ae4196edfb7d5f8bb127eb28115302c165ab62 Mon Sep 17 00:00:00 2001 From: KoNekoD <108808201+KoNekoD@users.noreply.github.com> Date: Tue, 21 May 2024 14:06:31 +0000 Subject: [PATCH 093/129] Update plugins.rst --- docs/plugins.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/plugins.rst b/docs/plugins.rst index 3618d4949..47df50fe1 100644 --- a/docs/plugins.rst +++ b/docs/plugins.rst @@ -25,6 +25,9 @@ instances running on different servers. `Supervisord-Monitor `_ Web-based dashboard written in PHP. +`Supervisord-Monitor 2 `_ + Modern and adaptive next gen web-based dashboard written in PHP. + `SupervisorUI `_ Another Web-based dashboard written in PHP. From 567f89b1a0ceeb5fe0ca38e88cf66b8315d7fa50 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Tue, 21 May 2024 09:54:30 -0700 Subject: [PATCH 094/129] Fix Python 3.5 tests on CI --- .github/workflows/main.yml | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 586abfbad..3a676aa6b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -44,12 +44,41 @@ jobs: - name: Run the end-to-end tests run: TOXENV=py34 END_TO_END=1 tox + tests_py35: + runs-on: ubuntu-20.04 + strategy: + fail-fast: false + + steps: + - uses: actions/checkout@v3 + + - name: Work around pip SSL cert verify error + run: sudo $PIP config set global.trusted-host 'pypi.python.org pypi.org files.pythonhosted.org' + + - name: Set up Python 3.5 + uses: actions/setup-python@v4 + with: + python-version: 3.5 + + - name: Install dependencies + run: $PIP install virtualenv tox + + - name: Set variable for TOXENV based on Python version + id: toxenv + run: python -c 'import sys; print("TOXENV=py%d%d" % (sys.version_info.major, sys.version_info.minor))' | tee -a $GITHUB_OUTPUT + + - name: Run the unit tests + run: TOXENV=${{steps.toxenv.outputs.TOXENV}} tox + + - name: Run the end-to-end tests + run: TOXENV=${{steps.toxenv.outputs.TOXENV}} END_TO_END=1 tox + tests_py3x: runs-on: ubuntu-20.04 strategy: fail-fast: false matrix: - python-version: [3.5, 3.6, 3.7, 3.8, 3.9, "3.10", 3.11, 3.12] + python-version: [3.6, 3.7, 3.8, 3.9, "3.10", 3.11, 3.12] steps: - uses: actions/checkout@v3 From 0e0d3ff14cb4f4b34d1c33cdb92d19a47f5f65bc Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Wed, 22 May 2024 08:17:17 -0700 Subject: [PATCH 095/129] Upgrade deprecated GitHub Actions steps --- .github/workflows/main.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3a676aa6b..93963bb48 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -15,7 +15,7 @@ jobs: fail-fast: false steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install dependencies run: $PIP install virtualenv tox @@ -50,13 +50,13 @@ jobs: fail-fast: false steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Work around pip SSL cert verify error run: sudo $PIP config set global.trusted-host 'pypi.python.org pypi.org files.pythonhosted.org' - name: Set up Python 3.5 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: 3.5 @@ -81,10 +81,10 @@ jobs: python-version: [3.6, 3.7, 3.8, 3.9, "3.10", 3.11, 3.12] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} @@ -108,7 +108,7 @@ jobs: fail-fast: false steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install dependencies run: $PIP install virtualenv tox @@ -124,10 +124,10 @@ jobs: python-version: [3.8] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} @@ -141,10 +141,10 @@ jobs: runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.8" From b537e0f1caaaec51ab6d4b22b6d792d4c8078765 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Sun, 14 Jul 2024 10:04:03 -0700 Subject: [PATCH 096/129] Use the same log level as the other fd event messages This should not be a warning because it only reports on the internal state of supervisord fd event handling; it's not something the user can affect. --- supervisor/supervisord.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/supervisor/supervisord.py b/supervisor/supervisord.py index 2265db9c7..8d9ebe30f 100755 --- a/supervisor/supervisord.py +++ b/supervisor/supervisord.py @@ -225,7 +225,7 @@ def runforever(self): else: # if the fd is not in combined_map, we should unregister it. otherwise, # it will be polled every time, which may cause 100% cpu usage - self.options.logger.warn('unexpected read event from fd %r' % fd) + self.options.logger.blather('unexpected read event from fd %r' % fd) try: self.options.poller.unregister_readable(fd) except: @@ -246,7 +246,7 @@ def runforever(self): except: combined_map[fd].handle_error() else: - self.options.logger.warn('unexpected write event from fd %r' % fd) + self.options.logger.blather('unexpected write event from fd %r' % fd) try: self.options.poller.unregister_writable(fd) except: From 273c8797209160f09cb3e24eb36c30b11dfd4fd4 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Sun, 14 Jul 2024 10:27:17 -0700 Subject: [PATCH 097/129] Fix Python 3.4 tests on CI --- .github/workflows/main.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 93963bb48..508a81def 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -32,6 +32,13 @@ jobs: strategy: fail-fast: false + env: + # Run actions/checkout@v3 + # /__e/node20/bin/node: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.25' not found (required by /__e/node20/bin/node) + # + # https://github.blog/changelog/2024-03-07-github-actions-all-actions-will-run-on-node20-instead-of-node16-by-default/ + ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true + steps: - uses: actions/checkout@v3 From c5be62e4e1827d32ca9a791640349ad3920a2c35 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Sun, 14 Jul 2024 10:33:34 -0700 Subject: [PATCH 098/129] Add changelog entry for 2a93d6b21edca4e8b00987c70a350ca626aebec8 --- CHANGES.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 27323b678..ce3214496 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,10 @@ 4.3.0.dev0 (Next Release) ------------------------- +- Fixed a bug where the poller would not unregister a closed + file descriptor under some circumstances, which caused excessive + polling, resulting in higher CPU usage. Patch by aftersnow. + - On Python 3.8 and later, ``setuptools`` is no longer a runtime dependency. Patch by Ofek Lev. From d97e37e45309df17ab91b2a7ff4d4b72363b8361 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Sun, 14 Jul 2024 10:47:55 -0700 Subject: [PATCH 099/129] Add changelog entry for 642468ffc5f2b98ebb08fb34b43d5826c8daff94 --- CHANGES.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index ce3214496..c09f64747 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,11 @@ file descriptor under some circumstances, which caused excessive polling, resulting in higher CPU usage. Patch by aftersnow. +- Fixed a bug where restarting ``supervisord`` may have failed with + the message ``Error: Another program is already listening + on a port that one of our HTTP servers is configured to use.`` + if an HTTP request was made during restart. Patch by Julien Le Cléach. + - On Python 3.8 and later, ``setuptools`` is no longer a runtime dependency. Patch by Ofek Lev. From 87c77bfefb341d6864f9b8c28138953a5b01f5c4 Mon Sep 17 00:00:00 2001 From: Liam Adamson Date: Thu, 18 Jul 2024 16:29:50 +0100 Subject: [PATCH 100/129] Add missing channel field to process log event docs Process log events (from both stdout and stderr) have channel information in their payload. The docs have been updated to reflect this. --- docs/events.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/events.rst b/docs/events.rst index 292528d9e..1c303b859 100644 --- a/docs/events.rst +++ b/docs/events.rst @@ -660,7 +660,7 @@ Body Description .. code-block:: text - processname:name groupname:name pid:pid + processname:name groupname:name pid:pid channel:stdout data ``PROCESS_LOG_STDERR`` Event Type @@ -680,7 +680,7 @@ Body Description .. code-block:: text - processname:name groupname:name pid:pid + processname:name groupname:name pid:pid channel:stderr data ``PROCESS_COMMUNICATION`` Event Type From 27efcd59b454e4f3a81e5e1b02ab0d8d0ff2f45f Mon Sep 17 00:00:00 2001 From: Colin Watson Date: Thu, 12 Dec 2024 22:31:34 +0000 Subject: [PATCH 101/129] Fix tests on Python 3.13 Python 3.13 added a `__firstlineno__` attribute to classes (https://docs.python.org/3/reference/datamodel.html#type.__firstlineno__) whose value is an int, so the approach taken by `SubprocessTests.test_getProcessStateDescription` to test only actual states no longer works. Exclude all attributes starting with `__` instead. --- supervisor/tests/test_process.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/supervisor/tests/test_process.py b/supervisor/tests/test_process.py index d9370e24c..24643b4dc 100644 --- a/supervisor/tests/test_process.py +++ b/supervisor/tests/test_process.py @@ -39,7 +39,7 @@ def test_getProcessStateDescription(self): from supervisor.states import ProcessStates from supervisor.process import getProcessStateDescription for statename, code in ProcessStates.__dict__.items(): - if isinstance(code, int): + if not statename.startswith("__"): self.assertEqual(getProcessStateDescription(code), statename) def test_ctor(self): From 10d929e428099c97aa3237f7d30decf30aa2d184 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Wed, 8 Jan 2025 10:50:19 -0800 Subject: [PATCH 102/129] Add Python 3.13 --- .github/workflows/main.yml | 2 +- setup.py | 1 + tox.ini | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 508a81def..16e4e3e86 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -85,7 +85,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.6, 3.7, 3.8, 3.9, "3.10", 3.11, 3.12] + python-version: [3.6, 3.7, 3.8, 3.9, "3.10", 3.11, 3.12, 3.13] steps: - uses: actions/checkout@v4 diff --git a/setup.py b/setup.py index 380dfa960..4fc9c2e4e 100644 --- a/setup.py +++ b/setup.py @@ -69,6 +69,7 @@ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", ] version_txt = os.path.join(here, 'supervisor/version.txt') diff --git a/tox.ini b/tox.ini index 9b3cb232f..387ee2183 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] envlist = - cover,cover3,docs,py27,py34,py35,py36,py37,py38,py39,py310,py311,py312 + cover,cover3,docs,py27,py34,py35,py36,py37,py38,py39,py310,py311,py312,py313 [testenv] deps = From e5701cdfb352da2808b10cd48d3ba9f301352aca Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Wed, 8 Jan 2025 10:56:33 -0800 Subject: [PATCH 103/129] Add changelog entry for 11cebbae34302028da022f34eaa90d862a4add26 --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index c09f64747..65d09b88d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -10,6 +10,9 @@ on a port that one of our HTTP servers is configured to use.`` if an HTTP request was made during restart. Patch by Julien Le Cléach. +- Fixed a unit test that failed only on Python 3.13. Only test code was + changed; no changes to ``supervisord`` itself. Patch by Colin Watson. + - On Python 3.8 and later, ``setuptools`` is no longer a runtime dependency. Patch by Ofek Lev. From ec2b255710b5c1b92fa035db5656a9559d8ccac7 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Sat, 18 Jan 2025 12:02:45 -0800 Subject: [PATCH 104/129] Avoid Pygments 2.19.0 to fix Sphinx docs build ``` /home/runner/work/supervisor/supervisor/docs/configuration.rst:1145:Could not lex literal_block '[program:cat]\ncommand=/bin/cat\nprocess_name=%(program_name)s\nnumprocs=1\ndirectory=/tmp\numask=022\npriority=999\nautostart=true\nautorestart=unexpected\nstartsecs=10\nstartretries=3\nexitcodes=0\nstopsignal=TERM\nstopwaitsecs=10\nstopasgroup=false\nkillasgroup=false\nuser=chrism\nredirect_stderr=false\nstdout_logfile=/a/path\nstdout_logfile_maxbytes=1MB\nstdout_logfile_backups=10\nstdout_capture_maxbytes=1MB\nstdout_events_enabled=false\nstderr_logfile=/a/path\nstderr_logfile_maxbytes=1MB\nstderr_logfile_backups=10\nstderr_capture_maxbytes=1MB\nstderr_events_enabled=false\nenvironment=A="1",B="2"\nserverurl=AUTO' as "ini". Highlighting skipped. ``` --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index 387ee2183..793a31d98 100644 --- a/tox.ini +++ b/tox.ini @@ -38,6 +38,7 @@ deps = [testenv:docs] deps = + pygments >= 2.19.1 # Sphinx build fails on 2.19.0 when highlighting ini block Sphinx readme setuptools >= 18.5 From 45da211aa6b3e043002df2e65ba3edd5ef5b9a2c Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Sun, 19 Jan 2025 14:57:37 -0800 Subject: [PATCH 105/129] Fix Python 3.4 tests on CI --- .github/workflows/main.yml | 47 ++++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 16e4e3e86..e19a90a71 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -28,22 +28,49 @@ jobs: tests_py34: runs-on: ubuntu-20.04 - container: python:3.4 strategy: fail-fast: false - env: - # Run actions/checkout@v3 - # /__e/node20/bin/node: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.25' not found (required by /__e/node20/bin/node) - # - # https://github.blog/changelog/2024-03-07-github-actions-all-actions-will-run-on-node20-instead-of-node16-by-default/ - ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true - steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + + - name: Build OpenSSL 1.0.2 (required by Python 3.4) + run: | + sudo apt-get install build-essential zlib1g-dev + + cd $RUNNER_TEMP + wget https://github.com/openssl/openssl/releases/download/OpenSSL_1_0_2u/openssl-1.0.2u.tar.gz + tar -xf openssl-1.0.2u.tar.gz + cd openssl-1.0.2u + ./config --prefix=/usr/local/ssl --openssldir=/usr/local/ssl shared zlib-dynamic + make + sudo make install + + echo CFLAGS="-I/usr/local/ssl/include $CFLAGS" >> $GITHUB_ENV + echo LDFLAGS="-L/usr/local/ssl/lib $LDFLAGS" >> $GITHUB_ENV + echo LD_LIBRARY_PATH="/usr/local/ssl/lib:$LD_LIBRARY_PATH" >> $GITHUB_ENV + + sudo ln -s /usr/local/ssl/lib/libssl.so.1.0.0 /usr/lib/libssl.so.1.0.0 + sudo ln -s /usr/local/ssl/lib/libcrypto.so.1.0.0 /usr/lib/libcrypto.so.1.0.0 + sudo ldconfig + + - name: Build Python 3.4 + run: | + sudo apt-get install build-essential libncurses5-dev libgdbm-dev libnss3-dev libreadline-dev zlib1g-dev + + cd $RUNNER_TEMP + wget -O cpython-3.4.10.zip https://github.com/python/cpython/archive/refs/tags/v3.4.10.zip + unzip cpython-3.4.10.zip + cd cpython-3.4.10 + ./configure + make + sudo make install + + python3.4 --version + python3.4 -c 'import ssl' - name: Install dependencies - run: $PIP install virtualenv tox + run: $PIP install virtualenv==20.4.7 tox==3.28.0 - name: Run the unit tests run: TOXENV=py34 tox From 8f2092500a845dfd740e5891d6beed329d8d4bd9 Mon Sep 17 00:00:00 2001 From: Louis Sautier Date: Sat, 25 Jan 2025 20:08:51 +0100 Subject: [PATCH 106/129] tests: fix test_socket_manager.py with PyPy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is basically reapplying 0ad4db8e1411 which was removed in 49b74cafb6e7. Without this patch, we got: ``` platform linux -- Python 3.10.14[pypy-7.3.17-final], pytest-8.3.4, pluggy-1.5.0 […] FAILED supervisor/tests/test_socket_manager.py::ProxyTest::test_on_delete - AssertionError: False is not true FAILED supervisor/tests/test_socket_manager.py::SocketManagerTest::test_logging - AssertionError: 1 != 2 FAILED supervisor/tests/test_socket_manager.py::SocketManagerTest::test_socket_lifecycle - AssertionError: True is not false FAILED supervisor/tests/test_socket_manager.py::SocketManagerTest::test_tcp_w_hostname - OSError: [Errno 98] Address already in use FAILED supervisor/tests/test_socket_manager.py::SocketManagerTest::test_tcp_w_ip - OSError: [Errno 98] Address already in use ``` --- supervisor/tests/test_socket_manager.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/supervisor/tests/test_socket_manager.py b/supervisor/tests/test_socket_manager.py index 4f20bca4f..f4fdf8db2 100644 --- a/supervisor/tests/test_socket_manager.py +++ b/supervisor/tests/test_socket_manager.py @@ -1,5 +1,6 @@ """Test suite for supervisor.socket_manager""" +import gc import os import unittest import socket @@ -49,6 +50,7 @@ def test_on_delete(self): proxy = self._makeOne(Subject(), on_delete=self.setOnDeleteCalled) self.assertEqual(5, proxy.getValue()) proxy = None + gc_collect() self.assertTrue(self.on_deleteCalled) class ReferenceCounterTest(unittest.TestCase): @@ -91,6 +93,9 @@ def test_decr_at_zero_raises_error(self): class SocketManagerTest(unittest.TestCase): + def tearDown(self): + gc_collect() + def _getTargetClass(self): from supervisor.socket_manager import SocketManager return SocketManager @@ -154,10 +159,12 @@ def test_socket_lifecycle(self): self.assertTrue(sock_manager.is_prepared()) self.assertFalse(sock_manager.socket.close_called) sock = None + gc_collect() # Socket not actually closed yet b/c ref ct is 1 self.assertTrue(sock_manager.is_prepared()) self.assertFalse(sock_manager.socket.close_called) sock2 = None + gc_collect() # Socket closed self.assertFalse(sock_manager.is_prepared()) self.assertTrue(sock_manager.socket.close_called) @@ -170,6 +177,7 @@ def test_socket_lifecycle(self): self.assertNotEqual(sock_id, sock3_id) # Drop ref ct to zero del sock3 + gc_collect() # Now assert that socket is closed self.assertFalse(sock_manager.is_prepared()) self.assertTrue(sock_manager.socket.close_called) @@ -184,6 +192,7 @@ def test_logging(self): self.assertEqual('Creating socket %s' % repr(conf), logger.data[0]) # socket close del sock + gc_collect() self.assertEqual(len(logger.data), 2) self.assertEqual('Closing socket %s' % repr(conf), logger.data[1]) @@ -232,3 +241,9 @@ def test_close_requires_prepared_socket(self): self.fail() except Exception as e: self.assertEqual(e.args[0], 'Socket has not been prepared') + +def gc_collect(): + if __pypy__ is not None: + gc.collect() + gc.collect() + gc.collect() From 7b4190831a5162f5b26a72a039770ad4dfb5f6a2 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Sat, 2 Aug 2025 13:01:41 -0700 Subject: [PATCH 107/129] Remove use of discontinued Ubuntu 20.04 image --- .github/workflows/main.yml | 105 ++++++++++++++++++++++--------------- 1 file changed, 64 insertions(+), 41 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e19a90a71..49c84385f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,11 +8,14 @@ env: pip --no-cache-dir" jobs: - tests_py27: - runs-on: ubuntu-20.04 - container: python:2.7 + tests_py2x: + runs-on: ubuntu-22.04 + container: + image: python:2.7 strategy: fail-fast: false + matrix: + toxenv: [py27, py27-configparser] steps: - uses: actions/checkout@v4 @@ -21,56 +24,66 @@ jobs: run: $PIP install virtualenv tox - name: Run the unit tests - run: TOXENV=py27 tox + run: TOXENV=${{ matrix.toxenv }} tox - name: Run the end-to-end tests - run: TOXENV=py27 END_TO_END=1 tox + run: TOXENV=${{ matrix.toxenv }} END_TO_END=1 tox - tests_py34: - runs-on: ubuntu-20.04 - strategy: - fail-fast: false + test_py34: + runs-on: ubuntu-22.04 + container: + image: ubuntu:20.04 + env: + LANG: C.UTF-8 steps: - uses: actions/checkout@v4 - - name: Build OpenSSL 1.0.2 (required by Python 3.4) + - name: Install build dependencies run: | - sudo apt-get install build-essential zlib1g-dev + apt-get update + apt-get install -y build-essential unzip wget \ + libncurses5-dev libgdbm-dev libnss3-dev \ + libreadline-dev zlib1g-dev + - name: Build OpenSSL 1.0.2 (required by Python 3.4) + run: | cd $RUNNER_TEMP wget https://github.com/openssl/openssl/releases/download/OpenSSL_1_0_2u/openssl-1.0.2u.tar.gz tar -xf openssl-1.0.2u.tar.gz cd openssl-1.0.2u ./config --prefix=/usr/local/ssl --openssldir=/usr/local/ssl shared zlib-dynamic make - sudo make install + make install - echo CFLAGS="-I/usr/local/ssl/include $CFLAGS" >> $GITHUB_ENV + echo CFLAGS="-I/usr/local/ssl/include $CFLAGS" >> $GITHUB_ENV echo LDFLAGS="-L/usr/local/ssl/lib $LDFLAGS" >> $GITHUB_ENV echo LD_LIBRARY_PATH="/usr/local/ssl/lib:$LD_LIBRARY_PATH" >> $GITHUB_ENV - sudo ln -s /usr/local/ssl/lib/libssl.so.1.0.0 /usr/lib/libssl.so.1.0.0 - sudo ln -s /usr/local/ssl/lib/libcrypto.so.1.0.0 /usr/lib/libcrypto.so.1.0.0 - sudo ldconfig + ln -s /usr/local/ssl/lib/libssl.so.1.0.0 /usr/lib/libssl.so.1.0.0 + ln -s /usr/local/ssl/lib/libcrypto.so.1.0.0 /usr/lib/libcrypto.so.1.0.0 + ldconfig - name: Build Python 3.4 run: | - sudo apt-get install build-essential libncurses5-dev libgdbm-dev libnss3-dev libreadline-dev zlib1g-dev - cd $RUNNER_TEMP wget -O cpython-3.4.10.zip https://github.com/python/cpython/archive/refs/tags/v3.4.10.zip unzip cpython-3.4.10.zip cd cpython-3.4.10 - ./configure + ./configure --with-ensurepip=install make - sudo make install + make install python3.4 --version python3.4 -c 'import ssl' + pip3.4 --version - - name: Install dependencies - run: $PIP install virtualenv==20.4.7 tox==3.28.0 + ln -s /usr/local/bin/python3.4 /usr/local/bin/python + ln -s /usr/local/bin/pip3.4 /usr/local/bin/pip + + - name: Install Python dependencies + run: | + $PIP install virtualenv==20.4.7 tox==3.14.0 - name: Run the unit tests run: TOXENV=py34 tox @@ -79,40 +92,49 @@ jobs: run: TOXENV=py34 END_TO_END=1 tox tests_py35: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 + container: + image: python:3.5 strategy: fail-fast: false steps: - uses: actions/checkout@v4 - - name: Work around pip SSL cert verify error - run: sudo $PIP config set global.trusted-host 'pypi.python.org pypi.org files.pythonhosted.org' + - name: Install dependencies + run: $PIP install virtualenv tox - - name: Set up Python 3.5 - uses: actions/setup-python@v5 - with: - python-version: 3.5 + - name: Run the unit tests + run: TOXENV=py35 tox + + - name: Run the end-to-end tests + run: TOXENV=py35 END_TO_END=1 tox + + tests_py36: + runs-on: ubuntu-22.04 + container: + image: python:3.6 + strategy: + fail-fast: false + + steps: + - uses: actions/checkout@v4 - name: Install dependencies run: $PIP install virtualenv tox - - name: Set variable for TOXENV based on Python version - id: toxenv - run: python -c 'import sys; print("TOXENV=py%d%d" % (sys.version_info.major, sys.version_info.minor))' | tee -a $GITHUB_OUTPUT - - name: Run the unit tests - run: TOXENV=${{steps.toxenv.outputs.TOXENV}} tox + run: TOXENV=py36 tox - name: Run the end-to-end tests - run: TOXENV=${{steps.toxenv.outputs.TOXENV}} END_TO_END=1 tox + run: TOXENV=py36 END_TO_END=1 tox tests_py3x: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: - python-version: [3.6, 3.7, 3.8, 3.9, "3.10", 3.11, 3.12, 3.13] + python-version: [3.7, 3.8, 3.9, "3.10", 3.11, 3.12, 3.13] steps: - uses: actions/checkout@v4 @@ -136,8 +158,9 @@ jobs: run: TOXENV=${{steps.toxenv.outputs.TOXENV}} END_TO_END=1 tox coverage_py27: - runs-on: ubuntu-20.04 - container: python:2.7 + runs-on: ubuntu-22.04 + container: + image: python:2.7 strategy: fail-fast: false @@ -151,7 +174,7 @@ jobs: run: TOXENV=cover tox coverage_py3x: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: @@ -172,7 +195,7 @@ jobs: run: TOXENV=cover3 tox docs: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 From 17c7ca3428a214bd29e69800dae8420acf794521 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Fri, 22 Aug 2025 12:15:23 -0700 Subject: [PATCH 108/129] Fix installation via "setup.py install" --- setup.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/setup.py b/setup.py index 4fc9c2e4e..0e59da239 100644 --- a/setup.py +++ b/setup.py @@ -22,11 +22,13 @@ elif (3, 0) < py_version < (3, 4): raise RuntimeError('On Python 3, Supervisor requires Python 3.4 or later') -# setuptools is required as a runtime dependency only on -# Python < 3.8. See the comments in supervisor/compat.py. -requires = [ - "setuptools; python_version < '3.8'", -] +# setuptools is required as a runtime dependency only on Python < 3.8. +# See the comments in supervisor/compat.py. An environment marker +# like "setuptools; python_version < '3.8'" is not used here because +# it breaks installation via "python setup.py install". +requires = [] +if py_version < (3, 8): + requires.append("setuptools") tests_require = [] testing_extras = tests_require + [ From 9f6cb64987a76de23778ddaa2d2d4518b5f0808d Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Fri, 22 Aug 2025 13:25:55 -0700 Subject: [PATCH 109/129] Add link to 'setup.py install' ticket --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 0e59da239..223f2e66b 100644 --- a/setup.py +++ b/setup.py @@ -25,7 +25,8 @@ # setuptools is required as a runtime dependency only on Python < 3.8. # See the comments in supervisor/compat.py. An environment marker # like "setuptools; python_version < '3.8'" is not used here because -# it breaks installation via "python setup.py install". +# it breaks installation via "python setup.py install". See also the +# discussion at: https://github.com/Supervisor/supervisor/issues/1692 requires = [] if py_version < (3, 8): requires.append("setuptools") From f82fe737be7ed7a5baa7dc2ec7276e084c6e94a0 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Fri, 22 Aug 2025 14:07:49 -0700 Subject: [PATCH 110/129] Raise on install if Python < 3.8 requirements are not met. Refs #1692 --- setup.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 223f2e66b..1521022cc 100644 --- a/setup.py +++ b/setup.py @@ -27,9 +27,17 @@ # like "setuptools; python_version < '3.8'" is not used here because # it breaks installation via "python setup.py install". See also the # discussion at: https://github.com/Supervisor/supervisor/issues/1692 -requires = [] if py_version < (3, 8): - requires.append("setuptools") + try: + import pkg_resources + import setuptools + except ImportError: + raise RuntimeError( + "On Python < 3.8, Supervisor requires setuptools as a runtime" + " dependency because pkg_resources is used to load plugins" + ) + +requires = [] tests_require = [] testing_extras = tests_require + [ From d95c205779b95d9da932790945ddd21e39be69b9 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Sat, 23 Aug 2025 09:17:22 -0700 Subject: [PATCH 111/129] Only check for pkg_resources as only it is required --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index 1521022cc..a9e4b97a5 100644 --- a/setup.py +++ b/setup.py @@ -30,7 +30,6 @@ if py_version < (3, 8): try: import pkg_resources - import setuptools except ImportError: raise RuntimeError( "On Python < 3.8, Supervisor requires setuptools as a runtime" From 2e5d59e99e596421a64525b1e094857a94392704 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Sat, 23 Aug 2025 09:40:59 -0700 Subject: [PATCH 112/129] Add notes to changelog and docs for installing on Python < 3.8 --- CHANGES.rst | 8 ++++++++ docs/installing.rst | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 65d09b88d..deeb3b3bb 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -16,6 +16,14 @@ - On Python 3.8 and later, ``setuptools`` is no longer a runtime dependency. Patch by Ofek Lev. +- On Python versions before 3.8, ``setuptools`` is still a runtime + dependency (for ``pkg_resources``) but it is no longer declared in + ``setup.py`` as such. This is because adding a conditional dependency + with an environment marker (``setuptools; python_version < '3.8'``) + breaks installation in some scenarios, e.g. ``setup.py install`` or + older versions of ``pip``. Ensure that ``setuptools`` in installed + if using Python before 3.8. + - ``supervisorctl`` now reads extra files included via the ``[include]`` section in ``supervisord.conf`` like ``supervisord`` does. This allows the ``[supervisorctl]`` section or ``[ctlplugin:x]`` sections to be in diff --git a/docs/installing.rst b/docs/installing.rst index f77d34109..0de601e77 100644 --- a/docs/installing.rst +++ b/docs/installing.rst @@ -22,6 +22,12 @@ to be the root user to install Supervisor successfully using You can also install supervisor in a virtualenv via ``pip``. +.. note:: + + If installing on a Python version before 3.8, first ensure that the + ``setuptools`` package is installed because it is a runtime + dependency of Supervisor. + Internet-Installing Without Pip ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -39,6 +45,12 @@ finally install Supervisor itself. need to be the root user to successfully invoke ``python setup.py install``. +.. note:: + + The ``setuptools`` package is required to run ``python setup.py install``. + On Python versions before 3.8, ``setuptools`` is also a runtime + dependency of Supervisor. + Installing To A System Without Internet Access ---------------------------------------------- @@ -63,6 +75,12 @@ Finally, run supervisor's ``python setup.py install``. need to be the root user to invoke ``python setup.py install`` successfully for each package. +.. note:: + + The ``setuptools`` package is required to run ``python setup.py install``. + On Python versions before 3.8, ``setuptools`` is also a runtime + dependency of Supervisor. + Installing a Distribution Package --------------------------------- From 1dff26317f88e2cf8a4990e7a04f14c7806ad03c Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Sat, 23 Aug 2025 09:54:14 -0700 Subject: [PATCH 113/129] Remove deprecated tests_require= and test_suite=. Refs #1675 --- setup.py | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/setup.py b/setup.py index a9e4b97a5..e13ac5d92 100644 --- a/setup.py +++ b/setup.py @@ -36,14 +36,6 @@ " dependency because pkg_resources is used to load plugins" ) -requires = [] - -tests_require = [] -testing_extras = tests_require + [ - 'pytest', - 'pytest-cov', - ] - from setuptools import setup, find_packages here = os.path.abspath(os.path.dirname(__file__)) try: @@ -102,14 +94,12 @@ author="Chris McDonough", author_email="chrism@plope.com", packages=find_packages(), - install_requires=requires, + install_requires=[], extras_require={ - 'testing': testing_extras, + 'test': ['pytest', 'pytest-cov'] }, - tests_require=tests_require, include_package_data=True, zip_safe=False, - test_suite="supervisor.tests", entry_points={ 'console_scripts': [ 'supervisord = supervisor.supervisord:main', From b9068d3de2b2bf18a9a414a173475b2239a7024e Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Sat, 23 Aug 2025 09:57:37 -0700 Subject: [PATCH 114/129] Fix typo in job name --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 49c84385f..9a3275e69 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -29,7 +29,7 @@ jobs: - name: Run the end-to-end tests run: TOXENV=${{ matrix.toxenv }} END_TO_END=1 tox - test_py34: + tests_py34: runs-on: ubuntu-22.04 container: image: ubuntu:20.04 From c3319bf10bf0538e9fab92c49ec28537c72eb8e2 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Sat, 23 Aug 2025 11:14:53 -0700 Subject: [PATCH 115/129] Fix typo in changelog --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index deeb3b3bb..806a53efb 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -21,7 +21,7 @@ ``setup.py`` as such. This is because adding a conditional dependency with an environment marker (``setuptools; python_version < '3.8'``) breaks installation in some scenarios, e.g. ``setup.py install`` or - older versions of ``pip``. Ensure that ``setuptools`` in installed + older versions of ``pip``. Ensure that ``setuptools`` is installed if using Python before 3.8. - ``supervisorctl`` now reads extra files included via the ``[include]`` From 523c42717333b13c6b16b346e0fb73833b6f64ce Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Sat, 23 Aug 2025 11:41:36 -0700 Subject: [PATCH 116/129] Add 4.3.0 release to changelog --- CHANGES.rst | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 806a53efb..8ac6ee55b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,14 @@ -4.3.0.dev0 (Next Release) +4.4.0.dev0 (Next Release) ------------------------- +- ``supervisorctl`` now reads extra files included via the ``[include]`` + section in ``supervisord.conf`` like ``supervisord`` does. This allows + the ``[supervisorctl]`` section or ``[ctlplugin:x]`` sections to be in + included files. Patch by François Granade. + +4.3.0 (2025-08-23) +------------------ + - Fixed a bug where the poller would not unregister a closed file descriptor under some circumstances, which caused excessive polling, resulting in higher CPU usage. Patch by aftersnow. @@ -24,11 +32,6 @@ older versions of ``pip``. Ensure that ``setuptools`` is installed if using Python before 3.8. -- ``supervisorctl`` now reads extra files included via the ``[include]`` - section in ``supervisord.conf`` like ``supervisord`` does. This allows - the ``[supervisorctl]`` section or ``[ctlplugin:x]`` sections to be in - included files. Patch by François Granade. - 4.2.5 (2022-12-23) ------------------ From bb1a276e53b0b782d399fae1b79fb214fb3a65b9 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Sat, 23 Aug 2025 11:41:53 -0700 Subject: [PATCH 117/129] Update version to 4.4.0.dev0 for next release --- supervisor/version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/supervisor/version.txt b/supervisor/version.txt index b75a1c1c0..5f8ceca6f 100644 --- a/supervisor/version.txt +++ b/supervisor/version.txt @@ -1 +1 @@ -4.3.0.dev0 +4.4.0.dev0 From 18c4f6b167b4d7d15928f949c0cf68e438f7b4ec Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Sun, 24 Aug 2025 18:49:22 -0700 Subject: [PATCH 118/129] Parse environment= using shlex in posix mode if possible Closes #328 References #873 Closes #1613 --- CHANGES.rst | 7 +++++++ supervisor/compat.py | 20 ++++++++++++++++++++ supervisor/datatypes.py | 10 ++++++++-- supervisor/tests/test_datatypes.py | 11 +++++++++++ 4 files changed, 46 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 8ac6ee55b..74914fb79 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,13 @@ 4.4.0.dev0 (Next Release) ------------------------- +- Parsing ``environment=`` in the config file now uses ``shlex`` in POSIX + mode instead of legacy mode to allow for escaped quotes in the values. + However, on Python 2 before 2.7.13 and Python 3 before 3.5.3, POSIX mode + can't be used because of a `bug `_ + in ``shlex``. If ``supervisord`` is run on a Python version with the bug, + it will fall back to legacy mode. Patch by Stefan Friesel. + - ``supervisorctl`` now reads extra files included via the ``[include]`` section in ``supervisord.conf`` like ``supervisord`` does. This allows the ``[supervisorctl]`` section or ``[ctlplugin:x]`` sections to be in diff --git a/supervisor/compat.py b/supervisor/compat.py index 023223fbd..004c00e0f 100644 --- a/supervisor/compat.py +++ b/supervisor/compat.py @@ -150,6 +150,26 @@ def is_text_stream(stream): except ImportError: # pragma: no cover from HTMLParser import HTMLParser +# Begin check for working shlex posix mode + +# https://github.com/Supervisor/supervisor/issues/328 +# https://github.com/Supervisor/supervisor/issues/873 +# https://bugs.python.org/issue21999 + +from shlex import shlex as _shlex + +_shlex_posix_expectations = { + 'foo="",bar=a': ['foo', '=', '', ',', 'bar', '=', 'a'], + "'')abc": ['', ')', 'abc'] +} + +shlex_posix_works = all( + list(_shlex(_input, posix=True)) == _expected + for _input, _expected in _shlex_posix_expectations.items() +) + +# End check for working shlex posix mode + # Begin importlib/setuptools compatibility code # Supervisor used pkg_resources (a part of setuptools) to load package diff --git a/supervisor/datatypes.py b/supervisor/datatypes.py index 24932d95e..7fa0dc2fe 100644 --- a/supervisor/datatypes.py +++ b/supervisor/datatypes.py @@ -5,6 +5,7 @@ import socket import shlex +from supervisor.compat import shlex_posix_works from supervisor.compat import urlparse from supervisor.compat import long from supervisor.loggers import getLevelNumByDescription @@ -68,7 +69,7 @@ def dict_of_key_value_pairs(arg): """ parse KEY=val,KEY2=val2 into {'KEY':'val', 'KEY2':'val2'} Quotes can be used to allow commas in the value """ - lexer = shlex.shlex(str(arg)) + lexer = shlex.shlex(str(arg), posix=shlex_posix_works) lexer.wordchars += '/.+-():' tokens = list(lexer) @@ -81,7 +82,12 @@ def dict_of_key_value_pairs(arg): if len(k_eq_v) != 3 or k_eq_v[1] != '=': raise ValueError( "Unexpected end of key/value pairs in value '%s'" % arg) - D[k_eq_v[0]] = k_eq_v[2].strip('\'"') + + k, v = k_eq_v[0], k_eq_v[2] + if not shlex_posix_works: + v = v.strip('\'"') + + D[k] = v i += 4 return D diff --git a/supervisor/tests/test_datatypes.py b/supervisor/tests/test_datatypes.py index e2801310d..b7f092f7c 100644 --- a/supervisor/tests/test_datatypes.py +++ b/supervisor/tests/test_datatypes.py @@ -8,6 +8,7 @@ from supervisor.tests.base import Mock, patch, sentinel from supervisor.compat import maxint +from supervisor.compat import shlex_posix_works from supervisor import datatypes @@ -164,6 +165,16 @@ def test_handles_newlines_inside_quotes(self): expected = {'foo': 'a\nb\nc'} self.assertEqual(actual, expected) + def test_handles_quotes_inside_quotes(self): + func = lambda: datatypes.dict_of_key_value_pairs('foo="\'\\""') + + if shlex_posix_works: + actual = func() + expected = {'foo': '\'"'} + self.assertEqual(actual, expected) + else: + self.assertRaises(ValueError, func) + def test_handles_empty_inside_quotes(self): actual = datatypes.dict_of_key_value_pairs('foo=""') expected = {'foo': ''} From 16912f0057c840ed6dde127045856a60d376e904 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Mon, 25 Aug 2025 08:53:22 -0700 Subject: [PATCH 119/129] Add value of autorestart= to getAllConfigInfo() Closes #1674 --- CHANGES.rst | 3 +++ supervisor/rpcinterface.py | 13 +++++++++++-- supervisor/tests/test_rpcinterfaces.py | 5 +++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 74914fb79..f3774cef7 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,9 @@ 4.4.0.dev0 (Next Release) ------------------------- +- Fixed a bug where the XML-RPC method ``supervisor.getAllConfigInfo()`` + did not return the value of the ``autorestart`` program option. + - Parsing ``environment=`` in the config file now uses ``shlex`` in POSIX mode instead of legacy mode to allow for escaped quotes in the values. However, on Python 2 before 2.7.13 and Python 3 before 3.5.3, POSIX mode diff --git a/supervisor/rpcinterface.py b/supervisor/rpcinterface.py index 6dfcd778c..f1c7c2ce9 100644 --- a/supervisor/rpcinterface.py +++ b/supervisor/rpcinterface.py @@ -10,6 +10,7 @@ from supervisor.datatypes import ( Automatic, + RestartWhenExitUnexpected, signal_number, ) @@ -568,6 +569,7 @@ def getAllConfigInfo(self): inuse = gconfig.name in self.supervisord.process_groups for pconfig in gconfig.process_configs: d = {'autostart': pconfig.autostart, + 'autorestart': pconfig.autorestart, 'directory': pconfig.directory, 'uid': pconfig.uid, 'command': pconfig.command, @@ -597,9 +599,16 @@ def getAllConfigInfo(self): 'stderr_syslog': pconfig.stderr_syslog, 'serverurl': pconfig.serverurl, } + # no support for these types in xml-rpc - d.update((k, 'auto') for k, v in d.items() if v is Automatic) - d.update((k, 'none') for k, v in d.items() if v is None) + for k, v in d.items(): + if v is Automatic: + d[k] = "auto" + elif v is None: + d[k] = "none" + elif v is RestartWhenExitUnexpected: + d[k] = "unexpected" + configinfo.append(d) configinfo.sort(key=lambda r: r['name']) diff --git a/supervisor/tests/test_rpcinterfaces.py b/supervisor/tests/test_rpcinterfaces.py index ef2f83ac5..578ccbc61 100644 --- a/supervisor/tests/test_rpcinterfaces.py +++ b/supervisor/tests/test_rpcinterfaces.py @@ -17,6 +17,7 @@ from supervisor.compat import as_string, PY2 from supervisor.datatypes import Automatic +from supervisor.datatypes import RestartWhenExitUnexpected class TestBase(unittest.TestCase): def setUp(self): @@ -1146,10 +1147,12 @@ def test_getAllConfigInfo(self): supervisord = DummySupervisor(options, 'foo') pconfig1 = DummyPConfig(options, 'process1', __file__, + autorestart=False, stdout_logfile=Automatic, stderr_logfile=Automatic, ) pconfig2 = DummyPConfig(options, 'process2', __file__, + autorestart=RestartWhenExitUnexpected, stdout_logfile=None, stderr_logfile=None, ) @@ -1160,6 +1163,7 @@ def test_getAllConfigInfo(self): interface = self._makeOne(supervisord) configs = interface.getAllConfigInfo() self.assertEqual(configs[0]['autostart'], True) + self.assertEqual(configs[0]['autorestart'], False) self.assertEqual(configs[0]['stopwaitsecs'], 10) self.assertEqual(configs[0]['stdout_events_enabled'], False) self.assertEqual(configs[0]['stderr_events_enabled'], False) @@ -1187,6 +1191,7 @@ def test_getAllConfigInfo(self): assert 'test_rpcinterfaces.py' in configs[0]['command'] self.assertEqual(configs[1]['autostart'], True) + self.assertEqual(configs[1]['autorestart'], "unexpected") self.assertEqual(configs[1]['stopwaitsecs'], 10) self.assertEqual(configs[1]['stdout_events_enabled'], False) self.assertEqual(configs[1]['stderr_events_enabled'], False) From 18438c590bb997368210e0f908c6150d24bf8617 Mon Sep 17 00:00:00 2001 From: yuk1pedia <18627927506@163.com> Date: Wed, 27 Aug 2025 16:41:44 +0800 Subject: [PATCH 120/129] fix: [supervisord] environment section does not properly escape %% --- supervisor/options.py | 2 +- supervisor/tests/test_options.py | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/supervisor/options.py b/supervisor/options.py index 271735200..5f1bc5ae5 100644 --- a/supervisor/options.py +++ b/supervisor/options.py @@ -657,7 +657,7 @@ def get(opt, default, **kwargs): section.nocleanup = boolean(get('nocleanup', 'false')) section.strip_ansi = boolean(get('strip_ansi', 'false')) - environ_str = get('environment', '') + environ_str = get('environment', '', do_expand=False) environ_str = expand(environ_str, expansions, 'environment') section.environment = dict_of_key_value_pairs(environ_str) diff --git a/supervisor/tests/test_options.py b/supervisor/tests/test_options.py index 4f3ff71de..cc9e8366f 100644 --- a/supervisor/tests/test_options.py +++ b/supervisor/tests/test_options.py @@ -3409,6 +3409,18 @@ def test_daemonize_notifies_poller_before_and_after_fork(self): instance.poller.before_daemonize.assert_called_once_with() instance.poller.after_daemonize.assert_called_once_with() + def test_options_environment_of_supervisord_with_escaped_chars(self): + text = lstrip(""" + [supervisord] + environment=VAR_WITH_P="some_value_%%_end" + """) + + instance = self._makeOne() + instance.configfile = StringIO(text) + instance.realize(args=[]) + options = instance.configroot.supervisord + self.assertEqual(options.environment, dict(VAR_WITH_P="some_value_%_end")) + class ProcessConfigTests(unittest.TestCase): def _getTargetClass(self): from supervisor.options import ProcessConfig From 364e2bfa082960fe85a56392d7eaa2701533b7b4 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Wed, 27 Aug 2025 15:12:44 -0700 Subject: [PATCH 121/129] Add changelog entry and more tests for 18438c590bb997368210e0f908c6150d24bf8617 --- CHANGES.rst | 5 +++++ supervisor/tests/test_options.py | 25 +++++++++++++++++++++++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index f3774cef7..c800af8a6 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,11 @@ - Fixed a bug where the XML-RPC method ``supervisor.getAllConfigInfo()`` did not return the value of the ``autorestart`` program option. +- Fixed a bug where an escaped percent sign (``%%``) could not be used + in ``environment=`` in the ``[supervisord]`` section of the config file. + The bug did not affect ``[program:x]`` sections, where an escaped + percent sign in ``environment=`` already worked. Patch by yuk1pedia. + - Parsing ``environment=`` in the config file now uses ``shlex`` in POSIX mode instead of legacy mode to allow for escaped quotes in the values. However, on Python 2 before 2.7.13 and Python 3 before 3.5.3, POSIX mode diff --git a/supervisor/tests/test_options.py b/supervisor/tests/test_options.py index cc9e8366f..7f6b329ee 100644 --- a/supervisor/tests/test_options.py +++ b/supervisor/tests/test_options.py @@ -1738,6 +1738,20 @@ def test_processes_from_section(self): self.assertEqual(pconfig.environment, {'KEY1':'val1', 'KEY2':'val2', 'KEY3':'0'}) + def test_processes_from_section_environment_with_escaped_chars(self): + instance = self._makeOne() + text = lstrip("""\ + [program:foo] + command = /bin/foo + environment=VAR_WITH_P="some_value_%%_end" + """) + from supervisor.options import UnhosedConfigParser + config = UnhosedConfigParser() + config.read_string(text) + pconfigs = instance.processes_from_section(config, 'program:foo', 'bar') + expected = {'VAR_WITH_P': 'some_value_%_end'} + self.assertEqual(pconfigs[0].environment, expected) + def test_processes_from_section_host_node_name_expansion(self): instance = self._makeOne() text = lstrip("""\ @@ -1933,7 +1947,7 @@ def test_options_with_environment_expansions(self): nocleanup = %(ENV_SUPD_NOCLEANUP)s childlogdir = %(ENV_HOME)s strip_ansi = %(ENV_SUPD_STRIP_ANSI)s - environment = FAKE_ENV_VAR=/some/path + environment = GLOBAL_ENV_VAR=%(ENV_SUPR_ENVIRONMENT_VALUE)s [inet_http_server] port=*:%(ENV_HTSRV_PORT)s @@ -1954,6 +1968,7 @@ def test_options_with_environment_expansions(self): startretries=%(ENV_CAT1_STARTRETRIES)s directory=%(ENV_CAT1_DIR)s umask=%(ENV_CAT1_UMASK)s + environment = PROGRAM_ENV_VAR=%(ENV_CAT1_ENVIRONMENT_VALUE)s """) from supervisor import datatypes from supervisor.options import UnhosedConfigParser @@ -1964,6 +1979,7 @@ def test_options_with_environment_expansions(self): 'ENV_HTSRV_PORT': '9210', 'ENV_HTSRV_USER': 'someuser', 'ENV_HTSRV_PASS': 'passwordhere', + 'ENV_SUPR_ENVIRONMENT_VALUE': 'from_supervisord_section', 'ENV_SUPD_LOGFILE_MAXBYTES': '51MB', 'ENV_SUPD_LOGFILE_BACKUPS': '10', 'ENV_SUPD_LOGLEVEL': 'info', @@ -1978,6 +1994,7 @@ def test_options_with_environment_expansions(self): 'ENV_CAT1_COMMAND_LOGDIR': '/path/to/logs', 'ENV_CAT1_PRIORITY': '3', 'ENV_CAT1_AUTOSTART': 'true', + 'ENV_CAT1_ENVIRONMENT_VALUE': 'from_program_section', 'ENV_CAT1_USER': 'root', # resolved to uid 'ENV_CAT1_STDOUT_LOGFILE': '/tmp/cat.log', 'ENV_CAT1_STDOUT_LOGFILE_MAXBYTES': '78KB', @@ -2043,7 +2060,11 @@ def test_options_with_environment_expansions(self): self.assertEqual(proc1.exitcodes, [0]) self.assertEqual(proc1.directory, '/tmp') self.assertEqual(proc1.umask, 2) - self.assertEqual(proc1.environment, dict(FAKE_ENV_VAR='/some/path')) + expected_env = { + 'GLOBAL_ENV_VAR': 'from_supervisord_section', + 'PROGRAM_ENV_VAR': 'from_program_section' + } + self.assertEqual(proc1.environment, expected_env) def test_options_supervisord_section_expands_here(self): instance = self._makeOne() From 64110792884d5c05b123361c763d7e6e620b29e4 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Thu, 9 Oct 2025 12:54:35 -0700 Subject: [PATCH 122/129] Add Python 3.14 --- .github/workflows/main.yml | 2 +- setup.py | 3 ++- tox.ini | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9a3275e69..e51c29608 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -134,7 +134,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.7, 3.8, 3.9, "3.10", 3.11, 3.12, 3.13] + python-version: [3.7, 3.8, 3.9, "3.10", 3.11, 3.12, 3.13, 3.14] steps: - uses: actions/checkout@v4 diff --git a/setup.py b/setup.py index e13ac5d92..ea2425ee1 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ raise RuntimeError('On Python 3, Supervisor requires Python 3.4 or later') # setuptools is required as a runtime dependency only on Python < 3.8. -# See the comments in supervisor/compat.py. An environment marker +# See the comments in supervisor/compat.py. An environment marker # like "setuptools; python_version < '3.8'" is not used here because # it breaks installation via "python setup.py install". See also the # discussion at: https://github.com/Supervisor/supervisor/issues/1692 @@ -72,6 +72,7 @@ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", ] version_txt = os.path.join(here, 'supervisor/version.txt') diff --git a/tox.ini b/tox.ini index 793a31d98..44bc9fccc 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] envlist = - cover,cover3,docs,py27,py34,py35,py36,py37,py38,py39,py310,py311,py312,py313 + cover,cover3,docs,py27,py34,py35,py36,py37,py38,py39,py310,py311,py312,py313,py314 [testenv] deps = From 578ea7aaa08378427d029ed662974b5334dda099 Mon Sep 17 00:00:00 2001 From: Stepan Blyschak Date: Fri, 24 Oct 2025 19:23:41 +0300 Subject: [PATCH 123/129] fix 1 sec delay on start We use supervisord in an environment that requires starting processes fast and even 1 sec counts. Signed-off-by: Stepan Blyschak --- supervisor/supervisord.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/supervisor/supervisord.py b/supervisor/supervisord.py index 8d9ebe30f..8d21eca33 100755 --- a/supervisor/supervisord.py +++ b/supervisor/supervisord.py @@ -173,11 +173,17 @@ def ordered_stop_groups_phase_2(self): def runforever(self): events.notify(events.SupervisorRunningEvent()) - timeout = 1 # this cannot be fewer than the smallest TickEvent (5) + first_poll = True socket_map = self.options.get_socket_map() while 1: + if first_poll: + timeout = 0 + first_poll = False + else: + timeout = 1 # this cannot not be fewer than the smallest TickEvent (5) + combined_map = {} combined_map.update(socket_map) combined_map.update(self.get_process_map()) From e85a1683dbdb758c5e554dae88d56126313f4278 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Fri, 24 Oct 2025 14:04:46 -0700 Subject: [PATCH 124/129] Do not change timeout variable on every iteration --- supervisor/supervisord.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/supervisor/supervisord.py b/supervisor/supervisord.py index 8d21eca33..2a7935ca5 100755 --- a/supervisor/supervisord.py +++ b/supervisor/supervisord.py @@ -173,17 +173,12 @@ def ordered_stop_groups_phase_2(self): def runforever(self): events.notify(events.SupervisorRunningEvent()) + timeout = 1 # this cannot be fewer than the smallest TickEvent (5) first_poll = True socket_map = self.options.get_socket_map() while 1: - if first_poll: - timeout = 0 - first_poll = False - else: - timeout = 1 # this cannot not be fewer than the smallest TickEvent (5) - combined_map = {} combined_map.update(socket_map) combined_map.update(self.get_process_map()) @@ -212,7 +207,12 @@ def runforever(self): if dispatcher.writable(): self.options.poller.register_writable(fd) - r, w = self.options.poller.poll(timeout) + if first_poll: + # initial timeout of 0 avoids delaying supervisord startup + r, w = self.options.poller.poll(0) + first_poll = False + else: + r, w = self.options.poller.poll(timeout) for fd in r: if fd in combined_map: From 895fbcec40b44a6db4e3c5d137e6d24f02ca48e6 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Fri, 24 Oct 2025 14:09:07 -0700 Subject: [PATCH 125/129] Add changelog entry for 578ea7aaa08378427d029ed662974b5334dda099 --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index c800af8a6..1daa44608 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,9 @@ 4.4.0.dev0 (Next Release) ------------------------- +- Fixed a bug where ``supervisord`` would wait 1 second on startup before + starting any programs. Patch by Stepan Blyshchak. + - Fixed a bug where the XML-RPC method ``supervisor.getAllConfigInfo()`` did not return the value of the ``autorestart`` program option. From 995bd6afe70f1978e96b42ba71dc33babdcddd67 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Sun, 26 Oct 2025 11:08:52 -0700 Subject: [PATCH 126/129] Move unreleased [include] changes to a feature branch --- CHANGES.rst | 5 - docs/configuration.rst | 10 +- supervisor/options.py | 92 +++---- .../tests/fixtures/example/included.conf | 3 - supervisor/tests/test_options.py | 245 +++++++----------- 5 files changed, 141 insertions(+), 214 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 1daa44608..e4adca268 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -19,11 +19,6 @@ in ``shlex``. If ``supervisord`` is run on a Python version with the bug, it will fall back to legacy mode. Patch by Stefan Friesel. -- ``supervisorctl`` now reads extra files included via the ``[include]`` - section in ``supervisord.conf`` like ``supervisord`` does. This allows - the ``[supervisorctl]`` section or ``[ctlplugin:x]`` sections to be in - included files. Patch by François Granade. - 4.3.0 (2025-08-23) ------------------ diff --git a/docs/configuration.rst b/docs/configuration.rst index 9029c2b4d..ce05d34f2 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -1184,6 +1184,12 @@ section, it must contain a single key named "files". The values in this key specify other configuration files to be included within the configuration. +.. note:: + + The ``[include]`` section is processed only by ``supervisord``. It is + ignored by ``supervisorctl``. + + ``[include]`` Section Values ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1208,10 +1214,6 @@ configuration. *Changed*: 3.3.0. Added support for the ``host_node_name`` expansion. - *Changed*: 4.3.0. Added support to :program:`supervisorctl` for reading - files specified in the ``[include]`` section. In previous versions, - the ``[include]`` section was only supported by :program:`supervisord`. - ``[include]`` Section Example ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/supervisor/options.py b/supervisor/options.py index 5f1bc5ae5..672dba113 100644 --- a/supervisor/options.py +++ b/supervisor/options.py @@ -104,9 +104,6 @@ def __init__(self, require_configfile=True): self.add(None, None, "h", "help", self.help) self.add(None, None, "?", None, self.help) self.add("configfile", None, "c:", "configuration=") - self.parse_criticals = [] - self.parse_warnings = [] - self.parse_infos = [] here = os.path.dirname(os.path.dirname(sys.argv[0])) searchpaths = [os.path.join(here, 'etc', 'supervisord.conf'), @@ -393,45 +390,6 @@ def import_spec(self, spec): """On failure, raises either AttributeError or ImportError""" return import_spec(spec) - def read_include_config(self, fp, parser, expansions): - if parser.has_section('include'): - parser.expand_here(self.here) - if not parser.has_option('include', 'files'): - raise ValueError(".ini file has [include] section, but no " - "files setting") - files = parser.get('include', 'files') - files = expand(files, expansions, 'include.files') - files = files.split() - if hasattr(fp, 'name'): - base = os.path.dirname(os.path.abspath(fp.name)) - else: - base = '.' - for pattern in files: - pattern = os.path.join(base, pattern) - filenames = glob.glob(pattern) - if not filenames: - self.parse_warnings.append( - 'No file matches via include "%s"' % pattern) - continue - for filename in sorted(filenames): - self.parse_infos.append( - 'Included extra file "%s" during parsing' % filename) - try: - parser.read(filename) - except ConfigParser.ParsingError as why: - raise ValueError(str(why)) - else: - parser.expand_here( - os.path.abspath(os.path.dirname(filename)) - ) - - def _log_parsing_messages(self, logger): - for msg in self.parse_criticals: - logger.critical(msg) - for msg in self.parse_warnings: - logger.warn(msg) - for msg in self.parse_infos: - logger.info(msg) class ServerOptions(Options): user = None @@ -490,6 +448,9 @@ def __init__(self): "s", "silent", flag=1, default=0) self.pidhistory = {} self.process_group_configs = [] + self.parse_criticals = [] + self.parse_warnings = [] + self.parse_infos = [] self.signal_receiver = SignalReceiver() self.poller = poller.Poller(self) @@ -618,8 +579,36 @@ def read_config(self, fp): expansions = {'here':self.here, 'host_node_name':host_node_name} expansions.update(self.environ_expansions) - - self.read_include_config(fp, parser, expansions) + if parser.has_section('include'): + parser.expand_here(self.here) + if not parser.has_option('include', 'files'): + raise ValueError(".ini file has [include] section, but no " + "files setting") + files = parser.get('include', 'files') + files = expand(files, expansions, 'include.files') + files = files.split() + if hasattr(fp, 'name'): + base = os.path.dirname(os.path.abspath(fp.name)) + else: + base = '.' + for pattern in files: + pattern = os.path.join(base, pattern) + filenames = glob.glob(pattern) + if not filenames: + self.parse_warnings.append( + 'No file matches via include "%s"' % pattern) + continue + for filename in sorted(filenames): + self.parse_infos.append( + 'Included extra file "%s" during parsing' % filename) + try: + parser.read(filename) + except ConfigParser.ParsingError as why: + raise ValueError(str(why)) + else: + parser.expand_here( + os.path.abspath(os.path.dirname(filename)) + ) sections = parser.sections() if not 'supervisord' in sections: @@ -1499,7 +1488,12 @@ def make_logger(self): maxbytes=self.logfile_maxbytes, backups=self.logfile_backups, ) - self._log_parsing_messages(self.logger) + for msg in self.parse_criticals: + self.logger.critical(msg) + for msg in self.parse_warnings: + self.logger.warn(msg) + for msg in self.parse_infos: + self.logger.info(msg) def make_http_servers(self, supervisord): from supervisor.http import make_http_servers @@ -1674,11 +1668,6 @@ def realize(self, *arg, **kw): if not self.args: self.interactive = 1 - format = '%(levelname)s: %(message)s\n' - logger = loggers.getLogger() - loggers.handle_stdout(logger, format) - self._log_parsing_messages(logger) - def read_config(self, fp): section = self.configroot.supervisorctl need_close = False @@ -1699,11 +1688,8 @@ def read_config(self, fp): parser.read_file(fp) except AttributeError: parser.readfp(fp) - if need_close: fp.close() - self.read_include_config(fp, parser, parser.expansions) - sections = parser.sections() if not 'supervisorctl' in sections: raise ValueError('.ini file does not include supervisorctl section') diff --git a/supervisor/tests/fixtures/example/included.conf b/supervisor/tests/fixtures/example/included.conf index 689c60557..82b33cc2d 100644 --- a/supervisor/tests/fixtures/example/included.conf +++ b/supervisor/tests/fixtures/example/included.conf @@ -1,5 +1,2 @@ [supervisord] childlogdir = %(here)s - -[supervisorctl] -history_file = %(here)s diff --git a/supervisor/tests/test_options.py b/supervisor/tests/test_options.py index 7f6b329ee..769d2cb70 100644 --- a/supervisor/tests/test_options.py +++ b/supervisor/tests/test_options.py @@ -257,106 +257,7 @@ def test_help(self): msg = 'A sample docstring for test_help\n' self.assertEqual(options.stdout.getvalue(), msg) -class IncludeTestsMixin(object): - def test_read_config_include_with_no_files_raises_valueerror(self): - instance = self._makeOne() - text = lstrip("""\ - [supervisord] - - [include] - ;no files= - """) - try: - instance.read_config(StringIO(text)) - self.fail("nothing raised") - except ValueError as exc: - self.assertEqual(exc.args[0], - ".ini file has [include] section, but no files setting") - - def test_read_config_include_with_no_matching_files_logs_warning(self): - instance = self._makeOne() - text = lstrip("""\ - [supervisord] - - [supervisorctl] - - [include] - files=nonexistent/* - """) - instance.read_config(StringIO(text)) - self.assertEqual(instance.parse_warnings, - ['No file matches via include "./nonexistent/*"']) - - def test_read_config_include_reads_files_in_sorted_order(self): - dirname = tempfile.mkdtemp() - conf_d = os.path.join(dirname, "conf.d") - os.mkdir(conf_d) - - supervisord_conf = os.path.join(dirname, "supervisord.conf") - text = lstrip("""\ - [supervisord] - - [supervisorctl] - - [include] - files=%s/conf.d/*.conf - """ % dirname) - with open(supervisord_conf, 'w') as f: - f.write(text) - - from supervisor.compat import letters - a_z = letters[:26] - for letter in reversed(a_z): - filename = os.path.join(conf_d, "%s.conf" % letter) - with open(filename, "w") as f: - f.write("[program:%s]\n" - "command=/bin/%s\n" % (letter, letter)) - - instance = self._makeOne() - try: - instance.read_config(supervisord_conf) - finally: - shutil.rmtree(dirname, ignore_errors=True) - expected_msgs = [] - for letter in sorted(a_z): - filename = os.path.join(conf_d, "%s.conf" % letter) - expected_msgs.append( - 'Included extra file "%s" during parsing' % filename) - self.assertEqual(instance.parse_infos, expected_msgs) - - def test_read_config_include_extra_file_malformed(self): - dirname = tempfile.mkdtemp() - conf_d = os.path.join(dirname, "conf.d") - os.mkdir(conf_d) - - supervisord_conf = os.path.join(dirname, "supervisord.conf") - text = lstrip("""\ - [supervisord] - - [include] - files=%s/conf.d/*.conf - """ % dirname) - with open(supervisord_conf, 'w') as f: - f.write(text) - - malformed_file = os.path.join(conf_d, "a.conf") - with open(malformed_file, 'w') as f: - f.write("[inet_http_server]\njunk\n") - - instance = self._makeOne() - try: - instance.read_config(supervisord_conf) - self.fail("nothing raised") - except ValueError as exc: - self.assertTrue('contains parsing errors:' in exc.args[0]) - self.assertTrue(malformed_file in exc.args[0]) - msg = 'Included extra file "%s" during parsing' % malformed_file - self.assertTrue(msg in instance.parse_infos) - finally: - shutil.rmtree(dirname, ignore_errors=True) - - -class ClientOptionsTests(unittest.TestCase, IncludeTestsMixin): +class ClientOptionsTests(unittest.TestCase): def _getTargetClass(self): from supervisor.options import ClientOptions return ClientOptions @@ -538,55 +439,7 @@ def test_options_unixsocket_configfile(self): instance.realize(args=[]) self.assertEqual(instance.serverurl, 'unix:///dev/null') - def test_read_config_include_reads_extra_files(self): - dirname = tempfile.mkdtemp() - conf_d = os.path.join(dirname, "conf.d") - os.mkdir(conf_d) - - supervisord_conf = os.path.join(dirname, "supervisord.conf") - text = lstrip("""\ - [include] - files=%s/conf.d/*.conf %s/conf.d/*.ini - """ % (dirname, dirname)) - with open(supervisord_conf, 'w') as f: - f.write(text) - - conf_file = os.path.join(conf_d, "a.conf") - with open(conf_file, 'w') as f: - f.write("[supervisorctl]\nhistory_file=%(here)s/sc_history\n") - - ini_file = os.path.join(conf_d, "a.ini") - with open(ini_file, 'w') as f: - f.write("[supervisorctl]\nserverurl=unix://%(here)s/supervisord.sock\n") - - instance = self._makeOne() - try: - instance.read_config(supervisord_conf) - finally: - shutil.rmtree(dirname, ignore_errors=True) - options = instance.configroot.supervisorctl - history_file = os.path.join(conf_d, 'sc_history') - self.assertEqual(options.serverurl, 'unix://' + conf_d + '/supervisord.sock') - self.assertEqual(options.history_file, history_file) - msg = 'Included extra file "%s" during parsing' % conf_file - self.assertTrue(msg in instance.parse_infos) - msg = 'Included extra file "%s" during parsing' % ini_file - self.assertTrue(msg in instance.parse_infos) - - def test_read_config_include_expands_here(self): - conf = os.path.join( - os.path.abspath(os.path.dirname(__file__)), 'fixtures', - 'include.conf') - root_here = os.path.dirname(conf) - include_here = os.path.join(root_here, 'example') - parser = self._makeOne() - parser.configfile = conf - parser.process_config_file(True) - section = parser.configroot.supervisorctl - self.assertEqual(section.history_file, include_here) - - -class ServerOptionsTests(unittest.TestCase, IncludeTestsMixin): +class ServerOptionsTests(unittest.TestCase): def _getTargetClass(self): from supervisor.options import ServerOptions return ServerOptions @@ -1061,6 +914,33 @@ def test_read_config_no_supervisord_section_raises_valueerror(self): self.assertEqual(exc.args[0], ".ini file does not include supervisord section") + def test_read_config_include_with_no_files_raises_valueerror(self): + instance = self._makeOne() + text = lstrip("""\ + [supervisord] + + [include] + ;no files= + """) + try: + instance.read_config(StringIO(text)) + self.fail("nothing raised") + except ValueError as exc: + self.assertEqual(exc.args[0], + ".ini file has [include] section, but no files setting") + + def test_read_config_include_with_no_matching_files_logs_warning(self): + instance = self._makeOne() + text = lstrip("""\ + [supervisord] + + [include] + files=nonexistent/* + """) + instance.read_config(StringIO(text)) + self.assertEqual(instance.parse_warnings, + ['No file matches via include "./nonexistent/*"']) + def test_read_config_include_reads_extra_files(self): dirname = tempfile.mkdtemp() conf_d = os.path.join(dirname, "conf.d") @@ -1096,6 +976,72 @@ def test_read_config_include_reads_extra_files(self): msg = 'Included extra file "%s" during parsing' % ini_file self.assertTrue(msg in instance.parse_infos) + def test_read_config_include_reads_files_in_sorted_order(self): + dirname = tempfile.mkdtemp() + conf_d = os.path.join(dirname, "conf.d") + os.mkdir(conf_d) + + supervisord_conf = os.path.join(dirname, "supervisord.conf") + text = lstrip("""\ + [supervisord] + + [include] + files=%s/conf.d/*.conf + """ % dirname) + with open(supervisord_conf, 'w') as f: + f.write(text) + + from supervisor.compat import letters + a_z = letters[:26] + for letter in reversed(a_z): + filename = os.path.join(conf_d, "%s.conf" % letter) + with open(filename, "w") as f: + f.write("[program:%s]\n" + "command=/bin/%s\n" % (letter, letter)) + + instance = self._makeOne() + try: + instance.read_config(supervisord_conf) + finally: + shutil.rmtree(dirname, ignore_errors=True) + expected_msgs = [] + for letter in sorted(a_z): + filename = os.path.join(conf_d, "%s.conf" % letter) + expected_msgs.append( + 'Included extra file "%s" during parsing' % filename) + self.assertEqual(instance.parse_infos, expected_msgs) + + def test_read_config_include_extra_file_malformed(self): + dirname = tempfile.mkdtemp() + conf_d = os.path.join(dirname, "conf.d") + os.mkdir(conf_d) + + supervisord_conf = os.path.join(dirname, "supervisord.conf") + text = lstrip("""\ + [supervisord] + + [include] + files=%s/conf.d/*.conf + """ % dirname) + with open(supervisord_conf, 'w') as f: + f.write(text) + + malformed_file = os.path.join(conf_d, "a.conf") + with open(malformed_file, 'w') as f: + f.write("[inet_http_server]\njunk\n") + + instance = self._makeOne() + try: + instance.read_config(supervisord_conf) + self.fail("nothing raised") + except ValueError as exc: + self.assertTrue('contains parsing errors:' in exc.args[0]) + self.assertTrue(malformed_file in exc.args[0]) + msg = 'Included extra file "%s" during parsing' % malformed_file + self.assertTrue(msg in instance.parse_infos) + finally: + shutil.rmtree(dirname, ignore_errors=True) + def test_read_config_include_expands_host_node_name(self): dirname = tempfile.mkdtemp() conf_d = os.path.join(dirname, "conf.d") @@ -3442,6 +3388,7 @@ def test_options_environment_of_supervisord_with_escaped_chars(self): options = instance.configroot.supervisord self.assertEqual(options.environment, dict(VAR_WITH_P="some_value_%_end")) + class ProcessConfigTests(unittest.TestCase): def _getTargetClass(self): from supervisor.options import ProcessConfig From 5bd1af9935a3acfca07a63e20f1627d2f2b51df5 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Sun, 26 Oct 2025 11:31:14 -0700 Subject: [PATCH 127/129] Remove dependency on the "mock" package on Python 3 --- tox.ini | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/tox.ini b/tox.ini index 44bc9fccc..837b419f0 100644 --- a/tox.ini +++ b/tox.ini @@ -4,30 +4,37 @@ envlist = [testenv] deps = - attrs < 21.1.0 # see https://github.com/python-attrs/attrs/pull/608 - pytest + attrs < 21.1.0 # see https://github.com/python-attrs/attrs/pull/608 pexpect == 4.7.0 # see https://github.com/Supervisor/supervisor/issues/1327 - mock >= 0.5.0 + pytest passenv = END_TO_END commands = pytest --capture=no {posargs} -[testenv:py27-configparser] -;see https://github.com/Supervisor/supervisor/issues/1230 +[testenv:py27] basepython = python2.7 deps = {[testenv]deps} - configparser + mock >= 0.5.0 passenv = {[testenv]passenv} commands = {[testenv]commands} +[testenv:py27-configparser] +;see https://github.com/Supervisor/supervisor/issues/1230 +basepython = python2.7 +deps = + {[testenv:py27]deps} + configparser +passenv = {[testenv:py27]passenv} +commands = {[testenv:py27]commands} + [testenv:cover] basepython = python2.7 -commands = - pytest --capture=no --cov=supervisor --cov-report=term-missing --cov-report=xml {posargs} deps = - {[testenv]deps} + {[testenv:py27]deps} pytest-cov +commands = + pytest --capture=no --cov=supervisor --cov-report=term-missing --cov-report=xml {posargs} [testenv:cover3] basepython = python3.8 From 5e94b0cf1ff5eebc3275f936556549610d7e5b78 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Sun, 26 Oct 2025 12:03:23 -0700 Subject: [PATCH 128/129] Avoid bdist_wheel.universal deprecation warning. Refs #1675 See: pypa/setuptools#4617 pypa/setuptools#4939 --- setup.cfg | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index e2b933446..8d41ae6c2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,12 @@ [easy_install] zip_ok = false +;Marking a wheel as universal with "universal = 1" was deprecated +;in Setuptools 75.1.0. Setting "python_tag = py2.py3" should do +;the equivalent on Setuptools 30.3.0 or later. +; +;https://github.com/pypa/setuptools/pull/4617 +;https://github.com/pypa/setuptools/pull/4939 +; [bdist_wheel] -universal = 1 +python_tag = py2.py3 From 5732a41c3393b15c06b665c5aea30920a42b324b Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Sun, 26 Oct 2025 13:46:00 -0700 Subject: [PATCH 129/129] Remove obsolete scripts/ directory --- CHANGES.rst | 4 ++ MANIFEST.in | 1 - docs/logging.rst | 4 -- supervisor/scripts/loop_eventgen.py | 32 ---------------- supervisor/scripts/loop_listener.py | 21 ----------- supervisor/scripts/sample_commevent.py | 25 ------------- supervisor/scripts/sample_eventlistener.py | 34 ----------------- .../scripts/sample_exiting_eventlistener.py | 37 ------------------- 8 files changed, 4 insertions(+), 154 deletions(-) delete mode 100755 supervisor/scripts/loop_eventgen.py delete mode 100755 supervisor/scripts/loop_listener.py delete mode 100755 supervisor/scripts/sample_commevent.py delete mode 100755 supervisor/scripts/sample_eventlistener.py delete mode 100755 supervisor/scripts/sample_exiting_eventlistener.py diff --git a/CHANGES.rst b/CHANGES.rst index e4adca268..bf9ea7536 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -19,6 +19,10 @@ in ``shlex``. If ``supervisord`` is run on a Python version with the bug, it will fall back to legacy mode. Patch by Stefan Friesel. +- The old example scripts in the ``supervisor/scripts/`` directory of + the package, which were largely undocumented, had no test coverage, and + were last updated over a decade ago, have been removed. + 4.3.0 (2025-08-23) ------------------ diff --git a/MANIFEST.in b/MANIFEST.in index ace534625..1fe78768f 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,7 +4,6 @@ include LICENSES.txt include README.rst include tox.ini include supervisor/version.txt -include supervisor/scripts/*.py include supervisor/skel/*.conf recursive-include supervisor/tests/fixtures *.conf *.py recursive-include supervisor/ui *.html *.css *.png *.gif diff --git a/docs/logging.rst b/docs/logging.rst index e509bd8b5..bec8ec33d 100644 --- a/docs/logging.rst +++ b/docs/logging.rst @@ -192,10 +192,6 @@ In this circumstance, :program:`supervisord` will emit a ``PROCESS_COMMUNICATIONS_STDOUT`` event with data in the payload of "Hello!". -An example of a script (written in Python) which emits a process -communication event is in the :file:`scripts` directory of the -supervisor package, named :file:`sample_commevent.py`. - The output of processes specified as "event listeners" (``[eventlistener:x]`` sections) is not processed this way. Output from these processes cannot enter capture mode. diff --git a/supervisor/scripts/loop_eventgen.py b/supervisor/scripts/loop_eventgen.py deleted file mode 100755 index 3a167ac55..000000000 --- a/supervisor/scripts/loop_eventgen.py +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env python - -# A process which emits a process communications event on its stdout, -# and subsequently waits for a line to be sent back to its stdin by -# loop_listener.py. - -import sys -import time -from supervisor import childutils - -def main(max): - start = time.time() - report = open('/tmp/report', 'w') - i = 0 - while 1: - childutils.pcomm.stdout('the_data') - sys.stdin.readline() - report.write(str(i) + ' @ %s\n' % childutils.get_asctime()) - report.flush() - i+=1 - if max and i >= max: - end = time.time() - report.write('%s per second\n' % (i / (end - start))) - sys.exit(0) - -if __name__ == '__main__': - max = 0 - if len(sys.argv) > 1: - max = int(sys.argv[1]) - main(max) - - diff --git a/supervisor/scripts/loop_listener.py b/supervisor/scripts/loop_listener.py deleted file mode 100755 index 74f9f1a8d..000000000 --- a/supervisor/scripts/loop_listener.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env python -u - -# An event listener that listens for process communications events -# from loop_eventgen.py and uses RPC to write data to the event -# generator's stdin. - -import os -from supervisor import childutils - -def main(): - rpcinterface = childutils.getRPCInterface(os.environ) - while 1: - headers, payload = childutils.listener.wait() - if headers['eventname'].startswith('PROCESS_COMMUNICATION'): - pheaders, pdata = childutils.eventdata(payload) - pname = '%s:%s' % (pheaders['processname'], pheaders['groupname']) - rpcinterface.supervisor.sendProcessStdin(pname, 'Got it yo\n') - childutils.listener.ok() - -if __name__ == '__main__': - main() diff --git a/supervisor/scripts/sample_commevent.py b/supervisor/scripts/sample_commevent.py deleted file mode 100755 index a7e6ad5ce..000000000 --- a/supervisor/scripts/sample_commevent.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env python - -# An example process which emits a stdout process communication event every -# second (or every number of seconds specified as a single argument). - -import sys -import time - -def write_stdout(s): - sys.stdout.write(s) - sys.stdout.flush() - -def main(sleep): - while 1: - write_stdout('') - write_stdout('the data') - write_stdout('') - time.sleep(sleep) - -if __name__ == '__main__': - if len(sys.argv) > 1: - main(float(sys.argv[1])) - else: - main(1) - diff --git a/supervisor/scripts/sample_eventlistener.py b/supervisor/scripts/sample_eventlistener.py deleted file mode 100755 index 8da5eaf2d..000000000 --- a/supervisor/scripts/sample_eventlistener.py +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env python -u - -# A sample long-running supervisor event listener which demonstrates -# how to accept event notifications from supervisor and how to respond -# properly. This demonstration does *not* use the -# supervisor.childutils module, which wraps the specifics of -# communications in higher-level API functions. If your listeners are -# implemented using Python, it is recommended that you use the -# childutils module API instead of modeling your scripts on the -# lower-level protocol example below. - -import sys - -def write_stdout(s): - sys.stdout.write(s) - sys.stdout.flush() - -def write_stderr(s): - sys.stderr.write(s) - sys.stderr.flush() - -def main(): - while 1: - write_stdout('READY\n') # transition from ACKNOWLEDGED to READY - line = sys.stdin.readline() # read header line from stdin - write_stderr(line) # print it out to stderr (testing only) - headers = dict([ x.split(':') for x in line.split() ]) - data = sys.stdin.read(int(headers['len'])) # read the event payload - write_stderr(data) # print the event payload to stderr (testing only) - write_stdout('RESULT 2\nOK') # transition from BUSY to ACKNOWLEDGED - #write_stdout('RESULT 4\nFAIL') # transition from BUSY TO ACKNOWLEDGED - -if __name__ == '__main__': - main() diff --git a/supervisor/scripts/sample_exiting_eventlistener.py b/supervisor/scripts/sample_exiting_eventlistener.py deleted file mode 100755 index 90f953415..000000000 --- a/supervisor/scripts/sample_exiting_eventlistener.py +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env python - -# A sample long-running supervisor event listener which demonstrates -# how to accept event notifications from supervisor and how to respond -# properly. It is the same as the sample_eventlistener.py script -# except it exits after each request (presumably to be restarted by -# supervisor). This demonstration does *not* use the -# supervisor.childutils module, which wraps the specifics of -# communications in higher-level API functions. If your listeners are -# implemented using Python, it is recommended that you use the -# childutils module API instead of modeling your scripts on the -# lower-level protocol example below. - -import sys - -def write_stdout(s): - sys.stdout.write(s) - sys.stdout.flush() - -def write_stderr(s): - sys.stderr.write(s) - sys.stderr.flush() - -def main(): - write_stdout('READY\n') # transition from ACKNOWLEDGED to READY - line = sys.stdin.readline() # read a line from stdin from supervisord - write_stderr(line) # print it out to stderr (testing only) - headers = dict([ x.split(':') for x in line.split() ]) - data = sys.stdin.read(int(headers['len'])) # read the event payload - write_stderr(data) # print the event payload to stderr (testing only) - write_stdout('RESULT 2\nOK') # transition from READY to ACKNOWLEDGED - # exit, if the eventlistener process config has autorestart=true, - # it will be restarted by supervisord. - -if __name__ == '__main__': - main() -