Skip to content

ADR 0029 — Время жизни upload и точное завершение чтения ​

Статус: принято. Дата: 2026-09-25.

Воспроизведённые проблемы ​

Общий connection timeout измерял отсутствие сетевого обмена и закрывал upload, когда паузу держал сам backend. Интеграционный тест с управляемой остановкой перед записью и настоящим сокетом воспроизводит этот отказ на aafeb55. Это подтверждает конкретный механизм; точная причина двух исторических ECONNRESET в больших локальных прогонах по имеющимся логам не установлена.

LocalBlobStore проверял наличие/размер перед выдачей файла, но после усечения между stat и чтением завершал поток успешно. Unit-тест воспроизводит короткое чтение; HTTP-клиент мог ждать оставшиеся байты до своего тайм-аута.

Решение ​

Upload-маршруты выделены из server/catalog-routes в самостоятельный registrar. JSON, URL, operationId, права, идемпотентность, лимиты admission и схема БД сохраняются. Полная загрузка и multipart используют один UploadReceiver; synchronous complete использует тот же механизм абсолютного deadline. Domain/application не зависят от HTTP и таймеров.

После получения ограниченного допуска сервер отключает общий TCP inactivity timer на время операции и применяет два независимых ограничения:

  • ARKVORY_UPLOAD_IDLE_TIMEOUT_MS — время ожидания очередного блока от отправителя, по умолчанию 30 000 ms. Оно отсчитывается только во время запроса следующего блока, исключая паузы записи, fsync, SQL и bandwidth governor.
  • ARKVORY_UPLOAD_DEADLINE_MS — абсолютное время принятой операции PUT content, PUT part или synchronous complete, по умолчанию 1 800 000 ms. Включает обработку и публикацию. Разрешены целые значения 1..1 800 000, idle не больше deadline.

После завершения восстанавливается прежний socket timeout; keep-alive и последующие запросы не наследуют отключение защиты. Внутренний HTTP-симулятор не имеет TCP-сокета, но общий deadline и правила чтения действуют и для него. До admission сохраняются обычные ограничения очереди/HTTP. Общий HTTP request timeout 30 минут остаётся дополнительным пределом.

Multipart-запрос с недопустимым индексом или отсутствующим checksum header получает 400 и Connection: close. Сервер не оставляет соединение с непрочитанным большим телом для keep-alive и фонового дочитывания вне upload admission. Этот отказ также проверен настоящим сокетом.

Отказ admission до начала приёма тела также закрывает соединение. Проверка удерживает единственный upload slot, отправляет full/part запросы с непрочитанным большим телом и подтверждает 503 после исчерпания времени очереди, отсутствие новых вызовов blob-store и очистку ожидающих операций.

Idle timeout уничтожает входящий поток, deadline и внешняя отмена закрывают соединение. Отмена передаётся до сценария хранения и повторно проверяется перед публикацией. Очистка staging и освобождение блокировок/допусков выполняются существующим finally-путём. Нельзя обещать немедленно прервать системный fsync: допуск удерживается до фактического выхода backend, чтобы не превысить ресурсный лимит. После потерянного ответа клиент проверяет сессию; timeout не равен удалению сессии или гарантированному rollback уже завершённого commit.

В stdout пишутся bounded warnings upload.input_timeout / upload.deadline с requestId, методом и шаблоном маршрута. Ключи, реальные URL/query, тела и внутренние сообщения исключений туда не добавляются. Запись использует прежний DiagnosticLogger и не ожидает диска/БД. Эти предупреждения сами по себе не добавляются в repository storage/events.

LocalBlobStore считает фактически прочитанные байты полного файла, диапазона и частей. Нормальное завершение допускается только при совпадении с ожидаемой длиной. Преждевременный EOF вызывает integrity_mismatch; начатый HTTP-ответ прерывается, допуск скачивания освобождается. Отмена чтения потребителем не выдаётся за EOF и не требует дочитывать весь файл. Это проверка длины, не замена SHA-256: SDK verified download и scrub сохраняют свои обязанности.

Download-stream отдельно регистрирует download.integrity_mismatch или download.read_failed, поскольку после отправки заголовков HTTP уже нельзя вернуть JSON с ошибкой. Внешняя отмена не записывается как ошибка чтения. Лог содержит requestId/шаблон маршрута, без файловых путей и exception text. Это общий путь полной, Range-раздачи и раздачи по пакету или пути файла.

Приёмка и границы ​

Новые unit/HTTP регрессии сначала воспроизведены на прежней реализации через гейты. Проверяются медленный backend, idle full/part, cleanup, повтор, deadline до публикации и при complete, потеря содержимого после проверки, full/Range и раздача по пути файла, освобождение admission, конфигурация и безопасные коды диагностики. Приёмка: quick и полный release, включая браузер и оба сценария 5 GiB.

Не изменяются HA, online GC, схема репликации и политика долгоживущих скачиваний. Reverse proxy также должен пропускать длительный upload/complete; прежний proxy timeout может оборвать запрос независимо от Arkvory.

Arkvory · ProAnimaStudio