From a0e480696278f7bc7949a5fde3cc53769235ebee Mon Sep 17 00:00:00 2001 From: Tanmoy Sarkar Date: Fri, 12 Jun 2026 23:30:48 +0530 Subject: [PATCH] refactor(companion): Cache configs for shutdown, drop dead setting companion_manager_stop_timeout() re-read the config file mid-shutdown, where a since-changed or removed file could raise. Cache the configs at manager spawn and use them; fall back to a guarded build when no manager has spawned yet. Drop companion_manager_reload_timeout: the setting was defined but never read anywhere. The reload path restarts the single manager via the main loop and does not bound its wait on this value. Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/design/companion-process-manager.md | 1 - gunicorn/arbiter.py | 14 +++++++++++++- gunicorn/config.py | 13 ------------- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/docs/design/companion-process-manager.md b/docs/design/companion-process-manager.md index 2fb501e3..796bebfc 100644 --- a/docs/design/companion-process-manager.md +++ b/docs/design/companion-process-manager.md @@ -182,7 +182,6 @@ companion_restart_delay = 5 # seconds; used when manager timeout is computed dynamically companion_manager_shutdown_buffer = 10 companion_manager_stop_timeout = None -companion_manager_reload_timeout = None companion_control_socket_mode = 0o600 ``` diff --git a/gunicorn/arbiter.py b/gunicorn/arbiter.py index d9674b13..29a314fb 100644 --- a/gunicorn/arbiter.py +++ b/gunicorn/arbiter.py @@ -67,6 +67,9 @@ class Arbiter: self.master_pid = 0 self.master_name = "Master" self.companion_manager_pid = 0 + # Configs of the currently running companion manager, cached at spawn so + # shutdown can size its wait without re-reading the config file. + self._companion_configs = [] cwd = util.getcwd() @@ -697,6 +700,7 @@ class Arbiter: pid = os.fork() if pid != 0: self.companion_manager_pid = pid + self._companion_configs = configs self.log.info("Companion manager started (pid:%s)", pid) return @@ -769,7 +773,15 @@ class Arbiter: """ if self.cfg.companion_manager_stop_timeout is not None: return self.cfg.companion_manager_stop_timeout - configs = build_companion_configs(self.cfg) + # Prefer the configs cached at spawn over re-reading the config file + # mid-shutdown, where a since-changed or removed file could raise. + configs = self._companion_configs + if not configs: + try: + configs = build_companion_configs(self.cfg) + except Exception: + self.log.exception("could not read companion config for shutdown") + return 0 if not configs: return 0 slowest = max(config.stop_timeout for config in configs) diff --git a/gunicorn/config.py b/gunicorn/config.py index 57a087fd..ccaa7844 100644 --- a/gunicorn/config.py +++ b/gunicorn/config.py @@ -2723,16 +2723,3 @@ class CompanionManagerStopTimeout(Setting): By default it uses the slowest companion ``stop_timeout`` plus a buffer. Example: ``companion_manager_stop_timeout = 120``. """ - - -class CompanionManagerReloadTimeout(Setting): - name = "companion_manager_reload_timeout" - section = "Companion Processes" - validator = validate_pos_int_or_none - default = None - desc = """\ - Seconds Gunicorn waits for the manager during reload. - - By default it uses the slowest companion ``reload_timeout`` plus a buffer. - Example: ``companion_manager_reload_timeout = 90``. - """