refactor: address PR review comments

1. Split respawning logic from reap_dirty_arbiter() into manage_dirty_arbiter()
   to avoid respawning during shutdown/re-exec (follows reap_workers pattern)

2. Reduce public API surface in __all__:
   - Keep errors, DirtyApp, client functions as public
   - Internal protocol helpers remain importable from submodules
   - DirtyArbiter and set_dirty_socket_path kept for gunicorn core
This commit is contained in:
Benoit Chesneau 2026-01-24 03:04:07 +01:00
parent 06aba09251
commit 3d9382b07c
2 changed files with 21 additions and 35 deletions

View File

@ -224,6 +224,7 @@ class Arbiter:
self.murder_workers() self.murder_workers()
self.manage_workers() self.manage_workers()
self.manage_dirty_arbiter()
except (StopIteration, KeyboardInterrupt): except (StopIteration, KeyboardInterrupt):
self.halt() self.halt()
except HaltServer as inst: except HaltServer as inst:
@ -764,7 +765,7 @@ class Arbiter:
sys.exit(0) sys.exit(0)
except SystemExit: except SystemExit:
raise raise
except Exception as e: except Exception:
self.log.exception("Exception in dirty arbiter process") self.log.exception("Exception in dirty arbiter process")
sys.exit(-1) sys.exit(-1)
@ -810,12 +811,18 @@ class Arbiter:
self.dirty_arbiter_pid = 0 self.dirty_arbiter_pid = 0
self.dirty_arbiter = None self.dirty_arbiter = None
# Respawn if we're still running and dirty workers are configured
if self.cfg.dirty_workers > 0 and self.cfg.dirty_apps:
self.log.info("Respawning dirty arbiter...")
self.spawn_dirty_arbiter()
except OSError as e: except OSError as e:
if e.errno == errno.ECHILD: if e.errno == errno.ECHILD:
self.dirty_arbiter_pid = 0 self.dirty_arbiter_pid = 0
self.dirty_arbiter = None self.dirty_arbiter = None
def manage_dirty_arbiter(self):
"""\
Maintain the dirty arbiter process by respawning if needed.
"""
if self.dirty_arbiter_pid:
return # Already running
if self.cfg.dirty_workers > 0 and self.cfg.dirty_apps:
self.log.info("Spawning dirty arbiter...")
self.spawn_dirty_arbiter()

View File

@ -27,31 +27,20 @@ from .errors import (
DirtyProtocolError, DirtyProtocolError,
) )
from .protocol import ( from .app import DirtyApp
DirtyProtocol,
make_request,
make_response,
make_error_response,
)
from .app import (
DirtyApp,
load_dirty_app,
load_dirty_apps,
)
from .worker import DirtyWorker
from .arbiter import DirtyArbiter
from .client import ( from .client import (
DirtyClient, DirtyClient,
get_dirty_client, get_dirty_client,
get_dirty_client_async, get_dirty_client_async,
set_dirty_socket_path, set_dirty_socket_path,
get_dirty_socket_path,
close_dirty_client, close_dirty_client,
close_dirty_client_async, close_dirty_client_async,
) )
# Internal imports used by gunicorn core (not part of public API)
from .arbiter import DirtyArbiter
__all__ = [ __all__ = [
# Errors # Errors
"DirtyError", "DirtyError",
@ -61,25 +50,15 @@ __all__ = [
"DirtyAppError", "DirtyAppError",
"DirtyAppNotFoundError", "DirtyAppNotFoundError",
"DirtyProtocolError", "DirtyProtocolError",
# Protocol # App base class
"DirtyProtocol",
"make_request",
"make_response",
"make_error_response",
# App
"DirtyApp", "DirtyApp",
"load_dirty_app",
"load_dirty_apps",
# Worker
"DirtyWorker",
# Arbiter
"DirtyArbiter",
# Client # Client
"DirtyClient", "DirtyClient",
"get_dirty_client", "get_dirty_client",
"get_dirty_client_async", "get_dirty_client_async",
"set_dirty_socket_path",
"get_dirty_socket_path",
"close_dirty_client", "close_dirty_client",
"close_dirty_client_async", "close_dirty_client_async",
# Internal (used by gunicorn core)
"DirtyArbiter",
"set_dirty_socket_path",
] ]