diff --git a/HISTORY/2026-08-14-session-log.md b/HISTORY/2026-08-14-session-log.md index 94a8cec..aee0b81 100644 --- a/HISTORY/2026-08-14-session-log.md +++ b/HISTORY/2026-08-14-session-log.md @@ -856,3 +856,32 @@ SIGKILL/рестарте пода запись не успевает → оче - nohup на ВМ: каждые 60с пишет VmRSS/Threads в `rss_monitor.log` (125 сэмплов). - Стартовый сэмпл 08:16 +0300: VmRSS=16588 kB, Threads=13. - Результат — после завершения мониторинга. + +## 2026-08-15 (вечер) — финальная полная регрессия на ВМ + флап fifo_dlq_probe + +**Запуск**: всё на ВМ (пользователь оффлайн), `tests/run_full_regression.sh` +в nohup → `full_regression.log`. Длительность 24с. + +**Результаты**: +- api_test.sh: **22/22** (PASS=22 FAIL=0) +- sdk_test.py: **15/15** (PASS=15 FAIL=0) +- fifo_dlq_probe.py: **флап** — 3 прогона: PASS / FAIL / PASS (dlq: count=0). + +**Разбор флапа dlq (сервис корректен, гонка в тесте)**: +- Механика сервиса: возврат видимости и перенос в DLQ делает тик PeriodicTasks + (период 1с) при истечении VisibilityTimeout. Перенос — при Retry >= MaxReceiveCount. +- Тест: VisibilityTimeout=1с, sleep 1.5с между receive, после 3-й попытки + СРАЗУ drain(dlq). При рассинхроне фазы тика (attempt 2 empty — тик не успел + вернуть сообщение за 1.5с) вторая доставка случается в attempt 3, и проверка + DLQ выполняется до тика переноса → dlq пуст → FAIL. Через ~2с сообщение + всё равно ушло бы в DLQ. +- Доказательство: `tests/dlq_deterministic_probe.py` — тот же сценарий, но с + ожиданием 2.5с после каждой доставки: **5/5 PASS** + (r1=1 r2=1 r3=0 dlq=1 main_left=0 во всех 5 прогонах). +- Вывод: перенос в DLQ работает стабильно; флап — исключительно тайминг теста. + +**Предложенная правка** `tests/fifo_dlq_probe.py` (жду «делай»): после цикла +попыток — опрос DLQ до 10с (drain каждые 1с) вместо мгновенного drain. + +**RSS-мониторинг P3.7**: продолжает писаться в `rss_monitor.log` (старт +08:16 +0300, VmRSS=16588 kB, Threads=13). diff --git a/tests/dlq_deterministic_probe.py b/tests/dlq_deterministic_probe.py new file mode 100644 index 0000000..9e0e080 --- /dev/null +++ b/tests/dlq_deterministic_probe.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +"""Детерминированная проверка DLQ-переноса (без гонок тайминга): +после каждой доставки ждём истечения visibility + тик PeriodicTasks (2.5с). +Если 5/5 PASS — сервис корректен, флап fifo_dlq_probe — гонка теста.""" +import json +import time +import uuid + +import boto3 +from botocore.config import Config + +ENDPOINT = "https://sqs.containerk8s.dev.nubes.ru" +REGION = "us-east-1" +sqs = boto3.client("sqs", endpoint_url=ENDPOINT, region_name=REGION, + config=Config(connect_timeout=10, read_timeout=30, retries={"max_attempts": 0})) + + +def drain(url, n=10): + msgs = [] + while True: + b = sqs.receive_message(QueueUrl=url, MaxNumberOfMessages=n).get("Messages", []) + if not b: + break + msgs.extend(b) + return msgs + + +def run(i): + uid = str(uuid.uuid4())[:8] + dlq_name = "dlqdet-%s-%d" % (uid, i) + main_name = "maindet-%s-%d" % (uid, i) + dlq_url = sqs.create_queue(QueueName=dlq_name)["QueueUrl"] + tenant = dlq_url.split("/")[-2] + arn = "arn:aws:sqs:%s:%s:%s" % (REGION, tenant, dlq_name) + policy = json.dumps({"deadLetterTargetArn": arn, "maxReceiveCount": "2"}) + main_url = sqs.create_queue( + QueueName=main_name, + Attributes={"VisibilityTimeout": "1", "RedrivePolicy": policy}, + )["QueueUrl"] + sqs.send_message(QueueUrl=main_url, MessageBody="to-dlq") + + r1 = sqs.receive_message(QueueUrl=main_url, MaxNumberOfMessages=1).get("Messages", []) + time.sleep(2.5) # visibility истёк + тик вернул (Retry=1) + r2 = sqs.receive_message(QueueUrl=main_url, MaxNumberOfMessages=1).get("Messages", []) + time.sleep(2.5) # visibility истёк + тик: Retry=2 >= maxReceiveCount=2 -> DLQ + r3 = sqs.receive_message(QueueUrl=main_url, MaxNumberOfMessages=1).get("Messages", []) + time.sleep(2.0) + dlq_msgs = drain(dlq_url) + main_left = drain(main_url) + + ok = (len(r1) == 1 and len(r2) == 1 and len(r3) == 0 + and len(dlq_msgs) == 1 and dlq_msgs[0]["Body"] == "to-dlq" + and len(main_left) == 0) + print("run %d: r1=%d r2=%d r3=%d dlq=%d main_left=%d -> %s" + % (i, len(r1), len(r2), len(r3), len(dlq_msgs), len(main_left), + "PASS" if ok else "FAIL"), flush=True) + try: + sqs.delete_queue(QueueUrl=main_url) + sqs.delete_queue(QueueUrl=dlq_url) + except Exception: + pass + return ok + + +ok = 0 +for i in range(1, 6): + try: + if run(i): + ok += 1 + except Exception as e: + print("run %d: EXC %s %s" % (i, type(e).__name__, str(e)[:120]), flush=True) +print("TOTAL: %d/5 PASS" % ok, flush=True) diff --git a/tests/run_full_regression.sh b/tests/run_full_regression.sh new file mode 100644 index 0000000..ebd6cb2 --- /dev/null +++ b/tests/run_full_regression.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# Полная финальная регрессия после фиксов v0.1.35. +# Запуск на ВМ: nohup bash tests/run_full_regression.sh > full_regression.log 2>&1 & +export AWS_ACCESS_KEY_ID=SSAK-fec713a719ad0b33c91fa54a +export AWS_SECRET_ACCESS_KEY=e898ed410a20ff166f51a52ba39a2954fb87e2da200b1648c3975772bfe9e2b4 +export ENDPOINT_URL=https://sqs.containerk8s.dev.nubes.ru +export REGION=us-east-1 + +cd "$HOME/terra/SQS-service" || exit 1 + +echo "=== FULL REGRESSION START $(date +%FT%T%z) ===" + +echo "--- [1/3] api_test.sh ---" +timeout 900 ./tests/api_test.sh +echo "api_test.sh exit=$?" + +echo "--- [2/3] sdk_test.py ---" +timeout 1200 python3 tests/sdk_test.py +echo "sdk_test.py exit=$?" + +echo "--- [3/3] fifo_dlq_probe.py ---" +timeout 1200 python3 tests/fifo_dlq_probe.py +echo "fifo_dlq_probe.py exit=$?" + +echo "=== FULL REGRESSION END $(date +%FT%T%z) ==="