mirror of
https://github.com/frappe/gunicorn.git
synced 2026-01-14 11:09:11 +08:00
Merge pull request #1394 from jonashaag/master
Add child_exit() callback to configuration
This commit is contained in:
commit
21ffa92be1
@ -515,8 +515,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."
|
||||||
@ -524,10 +525,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
|
||||||
@ -562,14 +565,15 @@ class Arbiter(object):
|
|||||||
self.cfg.pre_fork(self, worker)
|
self.cfg.pre_fork(self, worker)
|
||||||
pid = os.fork()
|
pid = os.fork()
|
||||||
if pid != 0:
|
if pid != 0:
|
||||||
|
worker.pid = pid
|
||||||
self.WORKERS[pid] = worker
|
self.WORKERS[pid] = worker
|
||||||
return pid
|
return pid
|
||||||
|
|
||||||
# Process Child
|
# Process Child
|
||||||
worker_pid = os.getpid()
|
worker.pid = os.getpid()
|
||||||
try:
|
try:
|
||||||
util._setproctitle("worker [%s]" % self.proc_name)
|
util._setproctitle("worker [%s]" % self.proc_name)
|
||||||
self.log.info("Booting worker with pid: %s", worker_pid)
|
self.log.info("Booting worker with pid: %s", worker.pid)
|
||||||
self.cfg.post_fork(self, worker)
|
self.cfg.post_fork(self, worker)
|
||||||
worker.init_process()
|
worker.init_process()
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
@ -587,7 +591,7 @@ class Arbiter(object):
|
|||||||
sys.exit(self.WORKER_BOOT_ERROR)
|
sys.exit(self.WORKER_BOOT_ERROR)
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
finally:
|
finally:
|
||||||
self.log.info("Worker exiting (pid: %s)", worker_pid)
|
self.log.info("Worker exiting (pid: %s)", worker.pid)
|
||||||
try:
|
try:
|
||||||
worker.tmp.close()
|
worker.tmp.close()
|
||||||
self.cfg.worker_exit(self, worker)
|
self.cfg.worker_exit(self, worker)
|
||||||
|
|||||||
@ -1650,6 +1650,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"
|
||||||
@ -1660,7 +1677,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.
|
||||||
|
|||||||
@ -39,6 +39,7 @@ class Worker(object):
|
|||||||
changes you'll want to do that in ``self.init_process()``.
|
changes you'll want to do that in ``self.init_process()``.
|
||||||
"""
|
"""
|
||||||
self.age = age
|
self.age = age
|
||||||
|
self.pid = "[booting]"
|
||||||
self.ppid = ppid
|
self.ppid = ppid
|
||||||
self.sockets = sockets
|
self.sockets = sockets
|
||||||
self.app = app
|
self.app = app
|
||||||
@ -58,10 +59,6 @@ class Worker(object):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "<Worker %s>" % self.pid
|
return "<Worker %s>" % self.pid
|
||||||
|
|
||||||
@property
|
|
||||||
def pid(self):
|
|
||||||
return os.getpid()
|
|
||||||
|
|
||||||
def notify(self):
|
def notify(self):
|
||||||
"""\
|
"""\
|
||||||
Your worker subclass must arrange to have this method called
|
Your worker subclass must arrange to have this method called
|
||||||
|
|||||||
@ -16,7 +16,7 @@ import gunicorn.arbiter
|
|||||||
|
|
||||||
class DummyApplication(gunicorn.app.base.BaseApplication):
|
class DummyApplication(gunicorn.app.base.BaseApplication):
|
||||||
"""
|
"""
|
||||||
Dummy application that has an default configuration.
|
Dummy application that has a default configuration.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def init(self, parser, opts, args):
|
def init(self, parser, opts, args):
|
||||||
@ -114,6 +114,34 @@ def test_arbiter_reexec_limit_child(fork):
|
|||||||
assert fork.called is False, "should not fork when arbiter is a child"
|
assert fork.called is False, "should not fork when arbiter is a child"
|
||||||
|
|
||||||
|
|
||||||
|
@mock.patch('os.fork')
|
||||||
|
def test_arbiter_calls_worker_exit(mock_os_fork):
|
||||||
|
mock_os_fork.return_value = 0
|
||||||
|
|
||||||
|
arbiter = gunicorn.arbiter.Arbiter(DummyApplication())
|
||||||
|
arbiter.cfg.settings['worker_exit'] = mock.Mock()
|
||||||
|
arbiter.pid = None
|
||||||
|
mock_worker = mock.Mock()
|
||||||
|
arbiter.worker_class = mock.Mock(return_value=mock_worker)
|
||||||
|
try:
|
||||||
|
arbiter.spawn_worker()
|
||||||
|
except SystemExit:
|
||||||
|
pass
|
||||||
|
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
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user