Стадия 2 — статика: атомарные скачивания больших видео, FloodWait→429, семафор с таймаутом #2
Reference in New Issue
Block a user
Delete Branch "%!s()"
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?
Цель
Убрать флаки отдачи
/media/...: невозможность отдать клиенту неполный файл, честный 429 на флуд вместо 404, ограниченное ожидание семафора.Полный план: plans/2026-07-05-stability-fix-plan.md
Контекст (что чиним)
temp_{fid}(api_server.py:379-396): параллельный Range-запрос браузерного плеера видит частичный файл и отдаёт его; при таймауте (120с на >100MB — заведомо мало) обрезок НЕ удаляется и целый час отдаётся как «готовый». Это основной источник «то работает, то нет».except errors.RPCError → 404(api_server.py:1001-1003):FloodWait— подклассRPCError→ при флуде медиа массово отдаёт 404, ридеры кэшируют «битые» картинки.temp_*по mtime>1ч — файл удаляется «из-под» зрителя длинного видео.HTTP_DOWNLOAD_SEMAPHORE(3): хвост запросов ждёт минутами без ответа.Задачи
_download_atomic(file_id, final_path, timeout): скачивание в{final}.part.{uuid.hex}→ валидация размера →os.rename(atomic) → вfinallyвсегда удалять.part.(таймаут/отмена/проигрыш гонки)temp_{fid})safe_download_media: параметрtimeout; для больших —min(1800, max(120, size // (256*1024))), env-конфигурируемоdict[(ch,pid,fid), asyncio.Future]— дедуп конкурентных скачиванийexcept errors.FloodWaitвget_mediaдоexcept errors.RPCError→ 429 +Retry-After: min(e.value + jitter, 300)os.utime) при каждой отдачеtemp_*-файлаacquireподwait_for(30)→ таймаут = 503 +Retry-After: 30.part.{hex}+ оставить старый.tmp.{hex}на переходный период.part.удалён; FloodWait → 429; свипер чистит оба суффиксаГде не наебаться
asyncio.create_task, а НЕ в корутине первого HTTP-запроса (отвал его клиента отменит скачивание и повесит всех ожидающих). В finally таска —set_result/set_exceptionИpopключа, иначе после первой ошибки ключ занят навсегда.download_mediaнигде не получает финальный путь напрямую (инвариант «финальное имя = полный файл»).stream_media— отдельная будущая фича.DoD
Неполный файл не может быть отдан ни при каком сценарии; флуд = 429; обрезки живут < 1 ч; активно просматриваемые файлы не удаляются.
Зависимости: стадия 1 (п. 1.3 — FloodWait в воркере).