mirror of
https://github.com/frappe/gunicorn.git
synced 2026-07-01 10:11:30 +08:00
feat(companion): Close Gunicorn-only fds in the manager child
The forked companion manager inherits the arbiter's HTTP listening sockets, its wakeup pipe, and the worker heartbeat files, none of which the manager uses. Close them in the child before running so the manager and the companions it forks do not pin the arbiter's fds. The manager creates its own signal pipe and control socket after the fork. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
22431f24e6
commit
31e08aac58
@ -691,7 +691,7 @@ No per-companion logic in Arbiter.
|
||||
- [x] Add manager spawn/reap logic in Arbiter.
|
||||
- [x] Add manager shutdown handling in Arbiter.
|
||||
- [x] Wire Gunicorn reload to manager `reread` or restart.
|
||||
- [ ] Close Gunicorn-only fds in manager child.
|
||||
- [x] Close Gunicorn-only fds in manager child.
|
||||
- [ ] Close manager-only fds in companion child.
|
||||
- [ ] Add parent-death cleanup.
|
||||
- [ ] Add lifecycle logs.
|
||||
|
||||
@ -699,6 +699,7 @@ class Arbiter:
|
||||
|
||||
# Process Child
|
||||
try:
|
||||
self._close_gunicorn_fds()
|
||||
util._setproctitle("companion manager [%s]" % self.proc_name)
|
||||
manager.run()
|
||||
sys.exit(0)
|
||||
@ -708,6 +709,24 @@ class Arbiter:
|
||||
self.log.exception("Exception in companion manager process")
|
||||
sys.exit(-1)
|
||||
|
||||
def _close_gunicorn_fds(self):
|
||||
"""Close fds the manager inherited from the arbiter but never uses.
|
||||
|
||||
The companion manager serves no HTTP traffic and does not run the
|
||||
arbiter's signal loop, so it drops the listening sockets, the arbiter's
|
||||
wakeup pipe, and the worker heartbeat files. Closing them keeps the
|
||||
manager (and the companions it forks) from pinning the arbiter's fds.
|
||||
"""
|
||||
for listener in self.LISTENERS:
|
||||
listener.close()
|
||||
for pipe_fd in self.PIPE:
|
||||
try:
|
||||
os.close(pipe_fd)
|
||||
except OSError:
|
||||
pass
|
||||
for worker in self.WORKERS.values():
|
||||
worker.tmp.close()
|
||||
|
||||
def reload_companion_manager(self):
|
||||
"""Restart the companion manager so it picks up the new configuration.
|
||||
|
||||
|
||||
@ -205,6 +205,20 @@ def test_stop_companion_manager_clears_pid_when_already_gone():
|
||||
assert arbiter.companion_manager_pid == 0
|
||||
|
||||
|
||||
def test_close_gunicorn_fds_in_manager_child():
|
||||
arbiter = gunicorn.arbiter.Arbiter(DummyApplication())
|
||||
listener = mock.Mock()
|
||||
worker = mock.Mock()
|
||||
arbiter.LISTENERS = [listener]
|
||||
arbiter.WORKERS = {1: worker}
|
||||
arbiter.PIPE = [7, 8]
|
||||
with mock.patch("os.close") as os_close:
|
||||
arbiter._close_gunicorn_fds()
|
||||
listener.close.assert_called_once_with()
|
||||
worker.tmp.close.assert_called_once_with()
|
||||
assert os_close.call_count == 2
|
||||
|
||||
|
||||
def test_reload_companion_manager_restarts_running():
|
||||
arbiter = gunicorn.arbiter.Arbiter(DummyApplication())
|
||||
arbiter.cfg.set("companion_workers", [{"name": "rq", "target": "pkg:run"}])
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user