Add child_exit() callback

This is similar to worker_exit() in that it is called just after a
worker has terminated, but it's called in the Gunicorn *master* process,
not the *child* process.
This commit is contained in:
Jonas Haag 2016-11-18 11:29:24 +01:00 committed by Jonas Haag
parent b3c4260706
commit 53fd1c1071
3 changed files with 35 additions and 3 deletions

View File

@ -498,8 +498,9 @@ class Arbiter(object):
if self.reexec_pid == wpid: if self.reexec_pid == wpid:
self.reexec_pid = 0 self.reexec_pid = 0
else: else:
# A worker said it cannot boot. We'll shutdown # A worker was terminated. If the termination reason was
# to avoid infinite start/stop cycles. # that it could not boot, we'll shut it down to avoid
# infinite start/stop cycles.
exitcode = status >> 8 exitcode = status >> 8
if exitcode == self.WORKER_BOOT_ERROR: if exitcode == self.WORKER_BOOT_ERROR:
reason = "Worker failed to boot." reason = "Worker failed to boot."
@ -507,10 +508,12 @@ class Arbiter(object):
if exitcode == self.APP_LOAD_ERROR: if exitcode == self.APP_LOAD_ERROR:
reason = "App failed to load." reason = "App failed to load."
raise HaltServer(reason, self.APP_LOAD_ERROR) raise HaltServer(reason, self.APP_LOAD_ERROR)
worker = self.WORKERS.pop(wpid, None) worker = self.WORKERS.pop(wpid, None)
if not worker: if not worker:
continue continue
worker.tmp.close() worker.tmp.close()
self.cfg.child_exit(self, worker)
except OSError as e: except OSError as e:
if e.errno != errno.ECHILD: if e.errno != errno.ECHILD:
raise raise

View File

@ -1634,6 +1634,23 @@ class PostRequest(Setting):
""" """
class ChildExit(Setting):
name = "child_exit"
section = "Server Hooks"
validator = validate_callable(2)
type = six.callable
def child_exit(server, worker):
pass
default = staticmethod(child_exit)
desc = """\
Called just after a worker has been exited, in the master process.
The callable needs to accept two instance variables for the Arbiter and
the just-exited Worker.
"""
class WorkerExit(Setting): class WorkerExit(Setting):
name = "worker_exit" name = "worker_exit"
section = "Server Hooks" section = "Server Hooks"
@ -1644,7 +1661,7 @@ class WorkerExit(Setting):
pass pass
default = staticmethod(worker_exit) default = staticmethod(worker_exit)
desc = """\ desc = """\
Called just after a worker has been exited. Called just after a worker has been exited, in the worker process.
The callable needs to accept two instance variables for the Arbiter and The callable needs to accept two instance variables for the Arbiter and
the just-exited Worker. the just-exited Worker.

View File

@ -55,6 +55,18 @@ def test_arbiter_calls_worker_exit(mock_os_fork):
arbiter.cfg.worker_exit.assert_called_with(arbiter, mock_worker) arbiter.cfg.worker_exit.assert_called_with(arbiter, mock_worker)
@mock.patch('os.waitpid')
def test_arbiter_reap_workers(mock_os_waitpid):
mock_os_waitpid.side_effect = [(42, 0), (0, 0)]
arbiter = gunicorn.arbiter.Arbiter(DummyApplication())
arbiter.cfg.settings['child_exit'] = mock.Mock()
mock_worker = mock.Mock()
arbiter.WORKERS = {42: mock_worker}
arbiter.reap_workers()
mock_worker.tmp.close.assert_called_with()
arbiter.cfg.child_exit.assert_called_with(arbiter, mock_worker)
class PreloadedAppWithEnvSettings(DummyApplication): class PreloadedAppWithEnvSettings(DummyApplication):
""" """
Simple application that makes use of the 'preload' feature to Simple application that makes use of the 'preload' feature to