Отказ сервиса через SSE (критично). Генератор прогресса писал в сокет только при изменении данных, поэтому на неизменном статусе (очередь, долгая склейка) не шло ни байта, разрыв клиента не обнаруживался и поток gthread жил до SSE_MAX_SECONDS. При limit_conn 24 на адрес трёх клиентов хватало, чтобы занять все 64 потока и положить сервис; закрытые вкладки копили «зомби»-потоки на 15 минут. Добавлен периодический комментарий в поток: запись на закрытом соединении падает сразу. Статус served добавлен в терминальные, иначе поток по уже выданной задаче крутился впустую. Бесшумная смерть воркеров. except Exception не ловит BaseException, и поток воркера умирал молча. Пул усыхал до нуля, после чего сервис отвечал вечным «Сервис перегружен» до перезапуска. Теперь сбой перехватывается, задача помечается ошибкой, воркер продолжает работу. Зависшие задачи не выселялись никогда: уборщик снимал только терминальные статусы, поэтому карточки копились до TASKS_MAX с тем же итогом — вечный отказ. Плюс их адреса лежали в памяти бесконечно, вопреки обещанию не хранить историю. Уборщик срезал файлы живых загрузок. Страховочное удаление по mtime шло по всему каталогу и не смотрело на активные задачи: загрузка длиннее FILE_TTL теряла уже скачанную дорожку и падала после часов работы. Гонка выдачи файла. Между проверкой существования и открытием уборщик успевал удалить файл, наружу летел 500 с трейсбеком и локальными путями — ровно то, от чего защищает очистка сообщений. Теперь честный 410. Отмена у самого финиша. API отвечал «отменено», а загрузка завершалась: файл занимал квоту, засчитывался в статистику, и ссылка уходила в публичную ленту, хотя пользователь нажал «Отмена». Порядок записи при ошибке. Статус выставлялся раньше текста, и клиент, закрывающий поток по терминальному статусу, успевал показать «Ошибка: » без причины — уже навсегда. Учёт выдачи не был атомарным: параллельные Range-запросы, которые шлют браузеры и менеджеры загрузок, кратно завышали публичную статистику. Проверено на живом сервисе: повторная выдача больше не увеличивает счётчик. Утечка дескрипторов: sqlite3.Connection.__exit__ фиксирует транзакцию, но не закрывает соединение — они держались до сборки мусора, пик 324 при лимите 1024. Добавлен фавикон и девять регрессионных тестов на перечисленное (всего 196). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AwY2Cg54RK7WZdvMaLV5Di
14 lines
753 B
XML
14 lines
753 B
XML