111 lines
3.1 KiB
Python
111 lines
3.1 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Shutdown supervisord when a selected program (or any program) exits.
|
|
|
|
Usage:
|
|
shutdown_on_exit.py <program_name_or_asterisk>
|
|
|
|
Examples:
|
|
shutdown_on_exit.py myprogram # only when 'myprogram' exits
|
|
shutdown_on_exit.py * # when ANY program exits
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import subprocess
|
|
|
|
# ---- Supervisor event listener protocol helpers ----
|
|
def read_header(fd):
|
|
"""Read a line like 'ver:3.0 server:supervisor serial:7 eventname:PROCESS_STATE_EXITED len:123'."""
|
|
line = fd.readline()
|
|
if not line:
|
|
return None
|
|
header = {}
|
|
for part in line.strip().split():
|
|
if ":" in part:
|
|
k, v = part.split(":", 1)
|
|
header[k] = v
|
|
return header
|
|
|
|
def read_payload(fd, length):
|
|
"""Read payload of given length into a string."""
|
|
if length is None:
|
|
return ""
|
|
length = int(length)
|
|
return fd.read(length)
|
|
|
|
def write_stdout(s):
|
|
sys.stdout.write(s)
|
|
sys.stdout.flush()
|
|
|
|
def write_stderr(s):
|
|
sys.stderr.write(s)
|
|
sys.stderr.flush()
|
|
|
|
def parse_processname_from_payload(payload):
|
|
"""
|
|
Payload is key=value lines. We want 'processname=<name>'.
|
|
Example payload:
|
|
processname:myprogram groupname:myprogram from_state:RUNNING pid:1234
|
|
Or sometimes 'processname=' is shown as 'processname:'
|
|
We'll accept both ':' and '=' separators to be safe.
|
|
"""
|
|
name = None
|
|
for token in payload.replace("\n", " ").split():
|
|
if token.startswith("processname"):
|
|
if ":" in token:
|
|
_, val = token.split(":", 1)
|
|
elif "=" in token:
|
|
_, val = token.split("=", 1)
|
|
else:
|
|
val = ""
|
|
name = val.strip()
|
|
return name
|
|
|
|
def do_shutdown():
|
|
try:
|
|
subprocess.run(
|
|
["supervisorctl", "shutdown"],
|
|
check=True,
|
|
stdout=subprocess.DEVNULL,
|
|
stderr=subprocess.DEVNULL,
|
|
)
|
|
except Exception as e:
|
|
write_stderr(f"shutdown_on_exit: failed to call supervisorctl shutdown: {e}\n")
|
|
|
|
def main():
|
|
if len(sys.argv) != 2:
|
|
write_stderr("Usage: shutdown_on_exit.py <program_name_or_asterisk>\n")
|
|
sys.exit(2)
|
|
|
|
# '*' or a specific program name
|
|
target = sys.argv[1]
|
|
|
|
write_stdout("READY\n")
|
|
|
|
while True:
|
|
header = read_header(sys.stdin)
|
|
if header is None:
|
|
break
|
|
|
|
payload = read_payload(sys.stdin, header.get("len"))
|
|
|
|
eventname = header.get("eventname", "")
|
|
if eventname == "PROCESS_STATE_EXITED":
|
|
procname = parse_processname_from_payload(payload) or ""
|
|
should_shutdown = (target == "*") or (procname == target)
|
|
|
|
if should_shutdown:
|
|
write_stderr(
|
|
f"shutdown_on_exit: triggering shutdown due to exit of '{procname}'\n"
|
|
)
|
|
# Acknowledge handling BEFORE shutdown to avoid protocol deadlock
|
|
write_stdout("RESULT 2\nOK")
|
|
do_shutdown()
|
|
sys.exit(0)
|
|
|
|
# Acknowledge we processed the event
|
|
write_stdout("RESULT 2\nOK")
|
|
|
|
if __name__ == "__main__":
|
|
main() |