fix(v2-scheduler): avoid restart loops when scheduler is disabled

This commit is contained in:
Alfredo Di Stasio
2026-03-20 14:49:48 +01:00
parent ad85e40688
commit 1aad6945c7
5 changed files with 61 additions and 2 deletions

View File

@ -59,6 +59,8 @@ DAILY_ORCHESTRATION_INTERVAL_SECONDS=86400
# Future optional scheduler loop settings (not enabled in base v2 runtime) # Future optional scheduler loop settings (not enabled in base v2 runtime)
SCHEDULER_ENABLED=0 SCHEDULER_ENABLED=0
SCHEDULER_INTERVAL_SECONDS=900 SCHEDULER_INTERVAL_SECONDS=900
# When scheduler is disabled but container is started, keep it idle (avoid restart loops)
SCHEDULER_DISABLED_SLEEP_SECONDS=300
# API safeguards (read-only API is optional) # API safeguards (read-only API is optional)
API_THROTTLE_ANON=100/hour API_THROTTLE_ANON=100/hour

View File

@ -282,6 +282,7 @@ Notes:
- image: `registry.younerd.org/hoopscout/scheduler:${APP_IMAGE_TAG:-latest}` - image: `registry.younerd.org/hoopscout/scheduler:${APP_IMAGE_TAG:-latest}`
- command: `/app/scripts/scheduler.sh` - command: `/app/scripts/scheduler.sh`
- interval: `DAILY_ORCHESTRATION_INTERVAL_SECONDS` - interval: `DAILY_ORCHESTRATION_INTERVAL_SECONDS`
- disabled idle interval: `SCHEDULER_DISABLED_SLEEP_SECONDS`
### Scheduler entrypoint/runtime expectations ### Scheduler entrypoint/runtime expectations
@ -290,6 +291,7 @@ Notes:
- scheduler is disabled unless: - scheduler is disabled unless:
- compose `scheduler` profile is started - compose `scheduler` profile is started
- `SCHEDULER_ENABLED=1` - `SCHEDULER_ENABLED=1`
- if scheduler service is started while disabled (`SCHEDULER_ENABLED=0`), it does not exit; it enters idle sleep mode to avoid restart loops with `restart: unless-stopped`
- this keeps default runtime simple while supporting daily automation - this keeps default runtime simple while supporting daily automation
### LBA extractor assumptions and limitations (MVP) ### LBA extractor assumptions and limitations (MVP)

View File

@ -75,6 +75,10 @@ services:
dockerfile: Dockerfile dockerfile: Dockerfile
env_file: env_file:
- .env - .env
environment:
SCHEDULER_ENABLED: ${SCHEDULER_ENABLED:-0}
SCHEDULER_DISABLED_SLEEP_SECONDS: ${SCHEDULER_DISABLED_SLEEP_SECONDS:-300}
DAILY_ORCHESTRATION_INTERVAL_SECONDS: ${DAILY_ORCHESTRATION_INTERVAL_SECONDS:-86400}
command: /app/scripts/scheduler.sh command: /app/scripts/scheduler.sh
depends_on: depends_on:
postgres: postgres:

View File

@ -2,8 +2,16 @@
set -e set -e
if [ "${SCHEDULER_ENABLED:-0}" != "1" ]; then if [ "${SCHEDULER_ENABLED:-0}" != "1" ]; then
echo "Scheduler disabled (SCHEDULER_ENABLED=${SCHEDULER_ENABLED:-0}). Exiting." DISABLED_SLEEP="${SCHEDULER_DISABLED_SLEEP_SECONDS:-300}"
exit 0 if [ "${DISABLED_SLEEP}" -lt 30 ]; then
echo "SCHEDULER_DISABLED_SLEEP_SECONDS must be >= 30"
exit 1
fi
echo "Scheduler disabled (SCHEDULER_ENABLED=${SCHEDULER_ENABLED:-0}). Entering idle mode with ${DISABLED_SLEEP}s sleep."
while true; do
echo "[$(date -u '+%Y-%m-%dT%H:%M:%SZ')] Scheduler disabled; sleeping for ${DISABLED_SLEEP}s."
sleep "${DISABLED_SLEEP}"
done
fi fi
INTERVAL="${DAILY_ORCHESTRATION_INTERVAL_SECONDS:-${SCHEDULER_INTERVAL_SECONDS:-86400}}" INTERVAL="${DAILY_ORCHESTRATION_INTERVAL_SECONDS:-${SCHEDULER_INTERVAL_SECONDS:-86400}}"

View File

@ -0,0 +1,43 @@
from __future__ import annotations
import os
import subprocess
import time
from pathlib import Path
def _repo_root() -> Path:
return Path(__file__).resolve().parent.parent
def test_scheduler_disabled_mode_stays_alive_without_exit_loop():
env = os.environ.copy()
env["SCHEDULER_ENABLED"] = "0"
env["SCHEDULER_DISABLED_SLEEP_SECONDS"] = "30"
process = subprocess.Popen(
["sh", "scripts/scheduler.sh"],
cwd=_repo_root(),
env=env,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
)
try:
time.sleep(1.0)
assert process.poll() is None
finally:
process.terminate()
process.wait(timeout=5)
def test_scheduler_compose_service_is_profile_gated():
compose_text = (_repo_root() / "docker-compose.yml").read_text(encoding="utf-8")
assert 'profiles: ["scheduler"]' in compose_text
assert "restart: unless-stopped" in compose_text
def test_scheduler_script_declares_idle_disabled_behavior():
scheduler_script = (_repo_root() / "scripts/scheduler.sh").read_text(encoding="utf-8")
assert "Entering idle mode" in scheduler_script
assert "SCHEDULER_DISABLED_SLEEP_SECONDS" in scheduler_script