template tutoring-admin-dashboard v1

This commit is contained in:
DevVibeCoder
2026-09-14 23:53:29 +05:30 Unverified
commit c4d9755262
70 changed files with 7955 additions and 0 deletions
+85
View File
@@ -0,0 +1,85 @@
#!/usr/bin/env python3
"""Exit the container when either half of the app has failed for good.
A container running two processes has a failure mode a single-process one does
not: supervisord keeps running after it gives up on a child, so the container
stays "up" with half an app in it. From the outside that is a healthy
container. From a browser it is a total outage, and nothing restarts it because
nothing has reported anything wrong.
This subscribes to ``PROCESS_STATE_FATAL`` — the state supervisord puts a
program in once it has stopped retrying — and shuts supervisord down, so the
container stops instead.
**The container exits 0, not non-zero**, and that is worth knowing before
choosing a restart policy. SIGTERM asks supervisord for an orderly shutdown, it
stops the surviving child properly, and it reports success at having done so.
The signal to an orchestrator is therefore *the container stopped*, not the
exit code. With ``restart: unless-stopped`` or ``always`` it comes back. With
``restart: on-failure`` it will NOT, because 0 is not a failure — which is a
defensible choice for an app that cannot start, but it should be a choice
rather than a surprise.
Making it exit non-zero would mean not running supervisord as PID 1, which
costs correct signal handling on ``docker stop`` — a worse trade for a rarer
event.
Why a script and not a one-line ``sh -c`` in the config: the event listener
protocol is a length-prefixed exchange on stdin/stdout, and getting it wrong
does not raise. It leaves a listener that sits reading forever while the thing
it was meant to watch stays broken — which is worse than not having it, because
it looks like it is working. The shell version of this really did do that.
"""
from __future__ import annotations
import os
import signal
import sys
def main() -> None:
while True:
# 1. Say we are ready for one event.
sys.stdout.write("READY\n")
sys.stdout.flush()
# 2. The header is a single line of `key:value` pairs, and it tells us
# how many bytes of payload follow. Reading the payload matters even
# though it is unused: leaving it in the pipe desynchronises every
# later exchange.
header = sys.stdin.readline()
if not header:
return # supervisord closed the pipe; it is going away anyway
fields = dict(
pair.split(":", 1) for pair in header.split() if ":" in pair
)
length = int(fields.get("len", 0) or 0)
payload = sys.stdin.read(length) if length else ""
if fields.get("eventname") == "PROCESS_STATE_FATAL":
name = dict(
pair.split(":", 1) for pair in payload.split() if ":" in pair
).get("processname", "a process")
sys.stderr.write(
f"quit_on_failure: {name} is FATAL — supervisord gave up "
f"restarting it. Stopping the container so this is visible as a "
f"failure rather than as a half-working app.\n"
)
sys.stderr.flush()
# 3. Answer before dying, so supervisord does not log a protocol
# error on top of the real one.
sys.stdout.write("RESULT 2\nOK")
sys.stdout.flush()
# SIGTERM, not SIGQUIT: supervisord treats it as an orderly
# shutdown and stops the surviving child properly, so nginx gets a
# chance to finish in-flight requests.
os.kill(os.getppid(), signal.SIGTERM)
return
sys.stdout.write("RESULT 2\nOK")
sys.stdout.flush()
if __name__ == "__main__":
main()