127 lines
3.4 KiB
Bash
127 lines
3.4 KiB
Bash
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
NAME="fixed-phase"
|
|
CYCLE_SECS="${PHASE_CYCLE_SECS:-900}"
|
|
OFFSET_SECS="${PHASE_OFFSET_SECS:-0}"
|
|
LOCK_FILE="${RPKI_HEAVY_LOCK:-/var/lock/rpki-heavy-run.lock}"
|
|
LOCK_WAIT_SECS="${LOCK_WAIT_SECS:-30}"
|
|
|
|
usage() {
|
|
cat <<'USAGE'
|
|
Usage:
|
|
fixed_phase_loop.sh [--name <name>] [--cycle-secs <seconds>] [--offset-secs <seconds>]
|
|
[--lock-file <path>] [--lock-wait-secs <seconds>] -- <command> [args...]
|
|
|
|
Runs one command at fixed wall-clock phases. Missed phases are skipped rather than caught up,
|
|
which keeps independent RP jobs from drifting into each other. A shared flock protects against
|
|
unexpected overruns.
|
|
USAGE
|
|
}
|
|
|
|
die() {
|
|
echo "error: $*" >&2
|
|
exit 2
|
|
}
|
|
|
|
is_non_negative_int() {
|
|
[[ "$1" =~ ^[0-9]+$ ]]
|
|
}
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--name)
|
|
shift
|
|
NAME="${1:?--name requires a value}"
|
|
;;
|
|
--cycle-secs)
|
|
shift
|
|
CYCLE_SECS="${1:?--cycle-secs requires a value}"
|
|
;;
|
|
--offset-secs)
|
|
shift
|
|
OFFSET_SECS="${1:?--offset-secs requires a value}"
|
|
;;
|
|
--lock-file)
|
|
shift
|
|
LOCK_FILE="${1:?--lock-file requires a value}"
|
|
;;
|
|
--lock-wait-secs)
|
|
shift
|
|
LOCK_WAIT_SECS="${1:?--lock-wait-secs requires a value}"
|
|
;;
|
|
--help|-h)
|
|
usage
|
|
exit 0
|
|
;;
|
|
--)
|
|
shift
|
|
break
|
|
;;
|
|
*)
|
|
die "unknown argument: $1"
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
[[ $# -gt 0 ]] || die "missing command after --"
|
|
is_non_negative_int "$CYCLE_SECS" || die "--cycle-secs must be a non-negative integer"
|
|
is_non_negative_int "$OFFSET_SECS" || die "--offset-secs must be a non-negative integer"
|
|
is_non_negative_int "$LOCK_WAIT_SECS" || die "--lock-wait-secs must be a non-negative integer"
|
|
(( CYCLE_SECS > 0 )) || die "--cycle-secs must be > 0"
|
|
(( OFFSET_SECS < CYCLE_SECS )) || die "--offset-secs must be < --cycle-secs"
|
|
|
|
mkdir -p "$(dirname "$LOCK_FILE")"
|
|
|
|
timestamp_utc() {
|
|
date -u +%Y-%m-%dT%H:%M:%SZ
|
|
}
|
|
|
|
format_epoch_utc() {
|
|
date -u -d "@$1" +%Y-%m-%dT%H:%M:%SZ
|
|
}
|
|
|
|
LAST_TARGET_EPOCH=-1
|
|
|
|
next_phase_epoch() {
|
|
local now="$1"
|
|
local shifted=$((now - OFFSET_SECS))
|
|
local remainder=$((shifted % CYCLE_SECS))
|
|
if (( remainder < 0 )); then
|
|
remainder=$((remainder + CYCLE_SECS))
|
|
fi
|
|
local sleep_secs=$((CYCLE_SECS - remainder))
|
|
if (( sleep_secs == CYCLE_SECS )); then
|
|
sleep_secs=0
|
|
fi
|
|
printf '%s\n' "$((now + sleep_secs))"
|
|
}
|
|
|
|
while true; do
|
|
now_epoch="$(date +%s)"
|
|
target_epoch="$(next_phase_epoch "$now_epoch")"
|
|
if (( target_epoch <= LAST_TARGET_EPOCH )); then
|
|
target_epoch=$((LAST_TARGET_EPOCH + CYCLE_SECS))
|
|
fi
|
|
LAST_TARGET_EPOCH="$target_epoch"
|
|
sleep_secs=$((target_epoch - now_epoch))
|
|
echo "[$(timestamp_utc)] $NAME next_phase=$(format_epoch_utc "$target_epoch") sleep=${sleep_secs}s cycle=${CYCLE_SECS}s offset=${OFFSET_SECS}s" >&2
|
|
if (( sleep_secs > 0 )); then
|
|
sleep "$sleep_secs"
|
|
fi
|
|
|
|
started_epoch="$(date +%s)"
|
|
echo "[$(timestamp_utc)] $NAME phase_start target=$(format_epoch_utc "$target_epoch") lock=$LOCK_FILE wait=${LOCK_WAIT_SECS}s" >&2
|
|
set +e
|
|
flock -w "$LOCK_WAIT_SECS" "$LOCK_FILE" "$@"
|
|
code=$?
|
|
set -e
|
|
ended_epoch="$(date +%s)"
|
|
if (( code == 0 )); then
|
|
echo "[$(timestamp_utc)] $NAME phase_done exit=0 elapsed=$((ended_epoch - started_epoch))s" >&2
|
|
else
|
|
echo "[$(timestamp_utc)] $NAME phase_done exit=$code elapsed=$((ended_epoch - started_epoch))s skipped_or_failed=1" >&2
|
|
fi
|
|
done
|