Public source validation / validate (push) Failing after 3m8s
64 lines
2.4 KiB
Bash
64 lines
2.4 KiB
Bash
#!/bin/sh
|
|
# Heartbeat liveness check for pulse-worker and pulse-agent.
|
|
#
|
|
# Full contract: docs/operations/WORKER_AGENT_HEALTHCHECK_CONTRACT.md
|
|
#
|
|
# Summary: the running process must write/touch $PULSE_HEARTBEAT_FILE at
|
|
# least every ~10s as long as its main loop is alive and making forward
|
|
# progress. This script treats the file's mtime as the source of truth
|
|
# (not its contents) so it never needs to parse a timestamp format.
|
|
#
|
|
# `docker compose up` (non-swarm) does not restart a container solely
|
|
# because its HEALTHCHECK reports "unhealthy" -- that status is only
|
|
# informational under plain Compose. To actually recover a hung-but-alive
|
|
# process without an external watcher (which would need Docker socket
|
|
# access, forbidden host-wide per ADR-0005), this script self-terminates
|
|
# PID 1 once staleness is confirmed, which turns "unhealthy" into a real
|
|
# container exit that `restart: unless-stopped` then recovers from.
|
|
#
|
|
# It deliberately does NOT self-terminate merely because the heartbeat file
|
|
# is absent shortly after container start (that's the normal boot path,
|
|
# handled separately below using the entrypoint-recorded start time), which
|
|
# avoids a restart loop on legitimately slow startups (DB dial, migrations
|
|
# wait, etc.).
|
|
set -eu
|
|
|
|
HEARTBEAT_FILE="${PULSE_HEARTBEAT_FILE:-/tmp/healthy}"
|
|
STARTED_AT_FILE="${PULSE_STARTED_AT_FILE:-/tmp/.pulse-started-at}"
|
|
MAX_AGE_SECONDS="${PULSE_HEARTBEAT_MAX_AGE_SECONDS:-45}"
|
|
|
|
now=$(date +%s)
|
|
|
|
self_restart() {
|
|
reason="$1"
|
|
echo "pulse-healthcheck: $reason; forcing container exit for restart" >&2
|
|
# Same-UID SIGKILL to PID 1 requires no capabilities (cap_drop: [ALL] is
|
|
# fine): standard POSIX signal permission only requires a matching UID,
|
|
# not CAP_KILL, when sender and target share a UID.
|
|
kill -9 1 2>/dev/null || true
|
|
}
|
|
|
|
if [ ! -f "$HEARTBEAT_FILE" ]; then
|
|
started_at=$(cat "$STARTED_AT_FILE" 2>/dev/null || echo "$now")
|
|
uptime=$((now - started_at))
|
|
if [ "$uptime" -gt "$MAX_AGE_SECONDS" ]; then
|
|
self_restart "no heartbeat ${uptime}s after container start"
|
|
else
|
|
echo "pulse-healthcheck: heartbeat file not yet written (uptime ${uptime}s)" >&2
|
|
fi
|
|
exit 1
|
|
fi
|
|
|
|
mtime=$(stat -c %Y "$HEARTBEAT_FILE" 2>/dev/null) || {
|
|
echo "pulse-healthcheck: cannot stat heartbeat file $HEARTBEAT_FILE" >&2
|
|
exit 1
|
|
}
|
|
age=$((now - mtime))
|
|
|
|
if [ "$age" -gt "$MAX_AGE_SECONDS" ]; then
|
|
self_restart "heartbeat stale (${age}s > ${MAX_AGE_SECONDS}s)"
|
|
exit 1
|
|
fi
|
|
|
|
exit 0
|