fix(media): follow-up self-heal — чистка негативкэша на рестарте + LRU-кап + тесты #48
Reference in New Issue
Block a user
Delete Branch "fix/43-selfheal-followup"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Follow-up по ретроспективному ревью
3c9ce72(issue #43): два реальных дефекта self-heal + тесты._download_failures) НЕ чистился после успешного self-heal рестарта → выздоровевший файл отдавал fast-503 до_DOWNLOAD_BACKOFF_MAX(до 1ч), сам ретраиться не мог (sweepercontinue, get_media 503) → прямое противоречие цели «картинки грузятся». Теперьtelegram_client._restart_clientна верифицированном рестарте (verify_get_meok) зовёт колбэк, зарегистрированныйapi_serverчерезclient.set_restart_callback→_download_failures.clear(). Колбэк НЕ срабатывает на упавшем/прерванном рестарте/SIGTERM-fallback; исключение колбэка подавляется. Зависимость односторонняя (нет circular import)._DOWNLOAD_BACKOFF_MAX3600→600с.verify_get_meпроверяет ГЛАВНЫЙ DC, а чистится media-негативкэш — ноclient.restart()пересобирает ВСЕ DC (включая media), так что дать файлам свежую попытку после верифицированного рестарта корректно; retry-storm ограничен (каждый retry timeout-bounded, заново взводит backoff). Если предпочитаешь мягче (halve backoff вместо clear, или ещё ниже кап) — скажи._download_failuresрос безлимитно →OrderedDict+ LRU-кап_DOWNLOAD_FAILURES_MAX=10000.How verified
.venv/bin/python -m pytest tests/ -q→ 579 passed (+10 self-heal). Внутреннее ревreview (все 8 критических инвариантов: колбэк только на verified success; нет circular import; ограниченность retry-storm; LRU-кап; консистентность ключа post-#24; без регрессий; тесты подлинны) + мутации: снятьverify_ok-гейт → падаетtest_failed_verify_does_not_clear_cache; снять eviction → падает LRU-тест; снять clear-хук → падает «cache cleared after restart».Тесты
hung-download отменяется по таймауту + освобождает слот; стрик → РОВНО один рестарт + verified-рестарт ЧИСТИТ кэш; провал verify → кэш НЕ чистится; консистентность ключа на всех путях; LRU-кап.
Checklist
_download_failuresограничен; pytest зелёный; поведение стрика/рестарта не изменено (только добавлена очистка)Ревью — #48
fix(media): follow-up self-heal (ретро-ревью #43) — чистка негативкэша на рестарте + кап + тестыВердикт: ПРАВКИ (CHANGES) — 2 правки (тест-покрытие backoff-капа + best-effort колбэка). Реализация корректна.
Полный веер из 9 аспектов по всему диффу PR (merge-base
4cf1293a..26c6a08e). Гейт зелёный:pytest tests/ -p no:cacheprovider→ 579 passed.Что проверено и корректно (клик)
verify_ok=Falseдо try,Trueтолько при успехеget_me();_on_restart_verified()зовётся ТОЛЬКО приverify_ok, ошибка колбэка глотается (не рвёт recovery),_restartingсбрасывается в finally. Критичное свойство «неудачный рестарт НЕ чистит негкэш» покрыто и мутационно-держится (test_failed_verify_does_not_clear_cache: убрать verify_ok-гейт → падает).move_to_end(key)на запись +while len > _DOWNLOAD_FAILURES_MAX: popitem(last=False); макс ровно cap, только что записанный ключ не вытесняется (moved-to-end первым). Мутацияlast=False→True→ тест ловит._download_failuresтолько на event-loop, без await между read/record и в колбэке — атомарно, гонок нет.note_download_ok, ниnote_download_timeout→ streak-нейтральны (проверено в except-ветке).Правки (сделать, потом ре-ревью)
api_server.py:163. Мутация: убратьmin(_DOWNLOAD_BACKOFF_MAX, …)→ все 10 self-heal тестов проходят (проверил сам). Кап load-bearing (держит backoff ≤600с, намеренно короткий под время self-heal). Fix:test_backoff_capped_at_max— записать достаточно фейлов, чтобы экспонента превысила 600с, ассертить чтоretry_not_before - now <= _DOWNLOAD_BACKOFF_MAX.telegram_client.py:250-254. try/except на месте и корректен, но нет теста, что бросающий колбэк НЕ рвёт recovery. Fix:test_restart_callback_error_does_not_abort— колбэк кидает, триггернуть verified-рестарт, ассертить что recovery завершилась (watchdog пере-армлен,_restartingсброшен) и ошибка залогирована.DO/ESCALATE нет сверх покрытия. Логика self-heal корректна и её ядро (verify-gating, LRU) мутационно-держится; недостают тесты на backoff-кап (мутационно не покрыт) и best-effort колбэка. После правок — PASS.
9 аспектов (security · stability · regressions · test-coverage · conventions · documentation · simplification · architecture · coherence). Verify-gating/LRU/streak-комментарий/coherence проверены независимо + мной; backoff-кап-гэп подтверждён моей мутацией. Escalate нет.
Поправил обе тест-правки (только тесты, прод-код байт-в-байт не тронут).
[test-coverage] backoff-кап —
test_backoff_capped_at_max: 8 фейлов подряд (сырая экспонента 60·2⁷=7680с) →_download_backoff_remaining(key) <= _DOWNLOAD_BACKOFF_MAX(600с, прижат к капу). Мутация: убратьmin(_DOWNLOAD_BACKOFF_MAX, …)→ backoff лезет к 7680с → тест краснеет.[test-coverage] best-effort колбэк —
test_restart_callback_error_does_not_abort: колбэк кидаетRuntimeErrorна верифицированном рестарте (restart/get_meзамоканы на успех,_start_watchdog/_restart_appзастаблены) → recovery ЗАВЕРШАЕТСЯ (watchdog пере-армлен[True], SIGTERM-fallback НЕ сработал[],_restarting=False) + ошибка залогирована. Мутация: убрать try/except вокруг колбэка → исключение уходит в внешний handler → SIGTERM-путь, watchdog не пере-армлен → тест краснеет.pytest tests/ -q→ 581 passed;git diffapi_server.py/telegram_client.py — пусто. Готово к повторному ревью.Ревью (раунд 2) — #48
fix(media): follow-up self-heal — чистка негативкэша на рестарте + LRU-капВердикт: PASS ✅ — обе правки раунда 1 закрыты.
Полный веер из 9 аспектов по всему диффу PR. Гейт зелёный:
pytest tests/ -p no:cacheprovider→ 581 passed (+2 к раунду 1). Дельта раунда 2 — ТЕСТ-ОНЛИ (implapi_server.py/telegram_client.pyбайт-идентичны раунду 1); обе новые проверки адверсариально подтверждены.Закрытие правок раунда 1 (мутационно подтверждено)
test_backoff_capped_at_max: записывает 8 фейлов (сырая экспонента 60·2⁷=7680с), ассертит что армленный дедлайн ≤_DOWNLOAD_BACKOFF_MAX(600с) И прижат к капу (>595с). Мутация (убратьmin()на api_server.py:163) → тест падает (7680>600). Я перепроверил сам.test_restart_callback_error_does_not_abort: регистрирует бросающий колбэк, гонит ВЕРИФИЦИРОВАННЫЙ рестарт, ассертит что recovery завершилась (watchdog пере-армлен, SIGTERM-фолбэка нет,_restarting=False) и ошибка залогирована. Мутация (снять try/except с колбэка → re-raise) → тест падает (recovery падает в SIGTERM). Я перепроверил сам.⛔ DROP — кодеру НЕ делать (лог калибровки)
[below-threshold]med/high[test-coverage]test_backoff_capped_at_maxчистит_download_failures(стр. 287) БЕЗ try/finally — если ассерт упадёт до клинапа, модульный dict остаётся грязным и может полить соседние тесты (соседtest_download_key_consistent_across_pathsиспользует try/finally). НЕ дефект: тест ПРОХОДИТ и чистит за собой на успехе; полюция только на ПАДАЮЩЕМ ассерте (в зелёном сьюте не случается). Рекомендация на будущее (не отдельный раунд): добавить autouse-фикстуру в conftest, чистящуюapi_server._download_failuresперед каждым тестом — ровно как существующая_reset_media_mime_cacheдля_mime_types; это захардит весь selfheal-сьют от полюции этого модульного стейта.DO/ESCALATE нет. Обе правки раунда 1 закрыты корректно и мутационно-держатся; impl не менялся; гейт зелёный. Approve.
9 аспектов. Обе новые проверки адверсариально верифицированы мной (мутация backoff-min + callback-reraise) + независимо двумя субагентами; impl байт-идентичен раунду 1. Test-pollution-на-падении — below-threshold (тест зелёный и само-чистится), в DROP с рекомендацией autouse-фикстуры. Escalate нет.