Skip to content

Commit 02582c6

Browse files
committed
systemd: send MAINPID updates on re-exec
1 parent d39a1aa commit 02582c6

File tree

3 files changed

+23
-2
lines changed

3 files changed

+23
-2
lines changed

gunicorn/arbiter.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,10 @@ def handle_hup(self):
256256
- Gracefully shutdown the old worker processes
257257
"""
258258
self.log.info("Hang up: %s", self.master_name)
259+
systemd.sd_notify("RELOADING=1\nSTATUS=Gunicorn arbiter reloading..", self.log)
259260
self.reload()
261+
# possibly premature, newly launched workers might have failed
262+
systemd.sd_notify("READY=1\nSTATUS=Gunicorn arbiter reloaded", self.log)
260263

261264
def handle_term(self):
262265
"SIGTERM handling"
@@ -332,6 +335,8 @@ def maybe_promote_master(self):
332335
self.pidfile.rename(self.cfg.pidfile)
333336
# reset proctitle
334337
util._setproctitle("master [%s]" % self.proc_name)
338+
# MAINPID does not change here, it was already set on fork
339+
systemd.sd_notify("READY=1\nMAINPID=%d\nSTATUS=Gunicorn arbiter promoted" % (os.getpid(), ), self.log)
335340

336341
def wakeup(self):
337342
"""\
@@ -437,6 +442,9 @@ def reexec(self):
437442
os.chdir(self.START_CTX['cwd'])
438443

439444
# exec the process using the original environment
445+
self.log.debug("exe=%r argv=%r" % (self.START_CTX[0], self.START_CTX['args']))
446+
# let systemd know are are in control
447+
systemd.sd_notify("READY=1\nMAINPID=%d\nSTATUS=Gunicorn arbiter re-exec" % (master_pid, ), self.log)
440448
os.execve(self.START_CTX[0], self.START_CTX['args'], environ)
441449

442450
def reload(self):
@@ -527,6 +535,8 @@ def reap_workers(self):
527535
if self.reexec_pid == wpid:
528536
self.reexec_pid = 0
529537
self.log.info("Master exited before promotion.")
538+
# let systemd know we are (back) in control
539+
systemd.sd_notify("READY=1\nMAINPID=%d\nSTATUS=Gunicorn arbiter re-exec aborted" % (os.getpid(), ), self.log)
530540
continue
531541
else:
532542
worker = self.WORKERS.pop(wpid, None)

gunicorn/systemd.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import os
66
import socket
7+
import time
78

89
SD_LISTEN_FDS_START = 3
910

@@ -66,6 +67,13 @@ def sd_notify(state, logger, unset_environment=False):
6667
if addr[0] == '@':
6768
addr = '\0' + addr[1:]
6869
sock.connect(addr)
70+
if state[-1] != '\n':
71+
state += "\n"
72+
# needed for notify-reload, but no harm in sending unconditionally
73+
# nsec = 10**-9, usec = 10**-6
74+
monotonic_usecs = time.clock_gettime_ns(time.CLOCK_MONOTONIC) // 1000
75+
state += "MONOTONIC_USEC=%d\n" % (monotonic_usecs, )
76+
logger.debug("sd_notify: %r" % (state, ))
6977
sock.sendall(state.encode('utf-8'))
7078
except Exception:
7179
logger.debug("Exception while invoking sd_notify()", exc_info=True)

tests/test_arbiter.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,17 +72,20 @@ def test_arbiter_stop_does_not_unlink_when_using_reuse_port(close_sockets):
7272
@mock.patch('os.getpid')
7373
@mock.patch('os.fork')
7474
@mock.patch('os.execve')
75-
def test_arbiter_reexec_passing_systemd_sockets(execve, fork, getpid):
75+
@mock.patch('gunicorn.systemd.sd_notify')
76+
def test_arbiter_reexec_passing_systemd_sockets(sd_notify, execve, fork, getpid):
7677
arbiter = gunicorn.arbiter.Arbiter(DummyApplication())
7778
arbiter.LISTENERS = [mock.Mock(), mock.Mock()]
7879
arbiter.systemd = True
7980
fork.return_value = 0
80-
getpid.side_effect = [2, 3]
81+
sd_notify.return_value = None
82+
getpid.side_effect = [2, 3, 3] # 2 getpid calls in new master
8183
arbiter.reexec()
8284
environ = execve.call_args[0][2]
8385
assert environ['GUNICORN_PID'] == '2'
8486
assert environ['LISTEN_FDS'] == '2'
8587
assert environ['LISTEN_PID'] == '3'
88+
sd_notify.assert_called_once()
8689

8790

8891
@mock.patch('os.getpid')

0 commit comments

Comments
 (0)