74 lines
No EOL
4.3 KiB
Bash
Executable file
74 lines
No EOL
4.3 KiB
Bash
Executable file
#!/bin/bash
|
||
# t-260929210755 / закрытие эскалации e-260929234911: рестарт демона лестницы
|
||
# БЕЗ убийства живых LLM-воркеров (рестарт при живом воркере = застрявшая
|
||
# running-задача, тот самый инцидент). Ждём «тихого окна» — нет процессов
|
||
# harness/_llm_worker.py, с подтверждением 3с, потом systemctl restart.
|
||
#
|
||
# Гарантия на гонку «проверил → стартовал новый воркер → рестарт»: orphan-requeue
|
||
# (commit 42743f9) возвращает running-задачи с мёртвым воркером в очередь —
|
||
# даже худший случай не теряет работу, просто повторит её.
|
||
#
|
||
# Использование: scripts/safe-restart.sh [max_wait_sec] (по умолчанию 3600)
|
||
set -u
|
||
MAX_WAIT="${1:-3600}"
|
||
LOG="${SIMBIOT_RESTART_LOG:-/tmp/simbiot-safe-restart.log}"
|
||
SERVICE="${SIMBIOT_SERVICE:-simbiot-harness}"
|
||
# якорь в конец cmdline: не матчит sh -c команды, которые лишь УПОМИНАют воркера
|
||
PAT='/harness/_llm_worker\.py$'
|
||
deadline=$(( $(date +%s) + MAX_WAIT ))
|
||
echo "[$(date -u '+%F %T')] safe-restart: ждём тихое окно (макс ${MAX_WAIT}s), сервис ${SERVICE}" >> "$LOG"
|
||
while :; do
|
||
if ! pgrep -f "$PAT" > /dev/null; then
|
||
sleep 3 # окно должно быть устойчивым
|
||
if ! pgrep -f "$PAT" > /dev/null; then
|
||
echo "[$(date -u '+%F %T')] safe-restart: тихое окно поймано" >> "$LOG"
|
||
break
|
||
fi
|
||
fi
|
||
if [ "$(date +%s)" -ge "$deadline" ]; then
|
||
echo "[$(date -u '+%F %T')] safe-restart: тихого окна нет ${MAX_WAIT}s — рестарт всё равно (orphan-requeue подхватит)" >> "$LOG"
|
||
break
|
||
fi
|
||
sleep 5
|
||
done
|
||
if systemctl restart "$SERVICE"; then
|
||
echo "[$(date -u '+%F %T')] safe-restart: демон ${SERVICE} перезапущен" >> "$LOG"
|
||
else
|
||
echo "[$(date -u '+%F %T')] safe-restart: FAIL — systemctl restart ${SERVICE}" >> "$LOG"
|
||
exit 1
|
||
fi
|
||
|
||
# ── пост-рестарт верификация: сек-тик НОВОГО демона отвечает на команду без LLM ──
|
||
# (инъекция синтетического сообщения в стор; через N секунд в сторе должен быть
|
||
# ReplySent от сек-тика — это доказывает, что новый код жив в продакшене)
|
||
sleep 6 # демон поднимается, первые сек-тики проходят
|
||
VERIFY_MSG_ID="${SIMBIOT_VERIFY_MSG_ID:-990102}"
|
||
cd /root/simbiot-harness || exit 1
|
||
./venv/bin/python - "$VERIFY_MSG_ID" <<'EOF' >> "$LOG" 2>&1
|
||
import sys, time, sqlite3
|
||
mid_num = sys.argv[1]
|
||
from harness import es, tg_inbox
|
||
mid = tg_inbox.receive_message({'update_id': int(mid_num), 'message': {
|
||
'message_id': int(mid_num),
|
||
'chat': {'id': 7569223131},
|
||
'from': {'id': 7569223131, 'username': 'alex_test'},
|
||
'text': '/лестница', 'date': int(time.time())}})
|
||
print(f"verify: инъекция {mid} (/лестница), ждём ответ сек-тика демона...")
|
||
time.sleep(8)
|
||
db = sqlite3.connect(es.DB_PATH); db.row_factory = sqlite3.Row
|
||
rows = db.execute("SELECT event_type, data FROM events WHERE aggregate_id=? ORDER BY id", (mid,)).fetchall()
|
||
types = [r["event_type"] for r in rows]
|
||
ok = "ReplySent" in types and "CommandHandled" in types
|
||
for r in rows:
|
||
print(f"verify: {r['event_type']} → {r['data'][:120]}")
|
||
print(f"verify: {'OK — сек-тик нового демона ответил на команду без LLM' if ok else 'FAIL — ответа нет, смотреть журнал демона'}")
|
||
sys.exit(0 if ok else 2)
|
||
EOF
|
||
rc=$?
|
||
if [ "$rc" -eq 0 ]; then
|
||
echo "[$(date -u '+%F %T')] safe-restart: верификация ПРОЙДЕНА (сек-тик отвечает на команды)" >> "$LOG"
|
||
elif [ "$rc" -eq 2 ]; then
|
||
echo "[$(date -u '+%F %T')] safe-restart: верификация ПРОВАЛЕНА — смотри журнал демона!" >> "$LOG"
|
||
journalctl -u "$SERVICE" --since "2 minutes ago" --no-pager | tail -20 >> "$LOG"
|
||
fi
|
||
exit "$rc" |