From bd0260040ca5f1b613e514d522403f9dfb9c7ad1 Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 22 Oct 2026 19:43:59 +0200 Subject: [PATCH] Replace obsolete media relay description with current attempt and buffer contracts --- lib/file_io.h | 2 +- src/media_delivery/sync.md | 110 ++++++++++++++++++------------------- 2 files changed, 54 insertions(+), 58 deletions(-) diff --git a/lib/file_io.h b/lib/file_io.h index 2717c93e..de8df4a2 100644 --- a/lib/file_io.h +++ b/lib/file_io.h @@ -1,4 +1,4 @@ -/* file_io — синхронные файловые операции для workers: UTF-8 пути, идентичность и публикация через соседний temp. +/* file_io — синхронные файловые операции: UTF-8 пути, идентичность и публикация через соседний temp. * inspect: 0 найден, 1 отсутствует, -1 ошибка; остальные операции 0/-1, paths также 1 для одного файла. * Temp обнулить → open → писать в file → finish → publish; discard обязателен также при ошибке. * Исходник и каталоги должны оставаться стабильными на время операции. Это не защита от подмены родительских каталогов. diff --git a/src/media_delivery/sync.md b/src/media_delivery/sync.md index 73183629..2feb5b21 100644 --- a/src/media_delivery/sync.md +++ b/src/media_delivery/sync.md @@ -181,71 +181,67 @@ status INTEGER — 0=processing (качается), 1=completed (скач Уникальный индекс: `(block_uuid, node_id)` — предотвращает две completed-записи для одного блока. -## 12. Relay-стриминг: передача блока в процессе загрузки +## 12. Попытки передачи и relay -### Принцип -Если узел C хочет скачать блок, а узел B уже качает этот блок от узла A: -1. C видит B в QUERY_RESP (благодаря BLOCK_PROCESSING, status=0) -2. C отправляет BLOCK_REQ к B -3. B принимает запрос, добавляет C в `relay_downstream` для этого блока -4. B получает BLOCK_CHUNK от A → пишет в файл → форвардит C -5. B получает BLOCK_DONE от A → форвардит C → удаляет relay-контекст +Актуальные структуры пакетов находятся в `media_delivery_proto.h`, владение загрузкой — +в `media_download.h`, источником байтов и выдачами — в `media_delivery.h`. +Реализация приёма, проверки, сборки и выдачи блоков работает синхронно в uasync. -### Flow control -- Relay-узел всегда отстаёт от апстрима: он не может слать быстрее, чем получает -- Каждый downstream хранит `sent_offset` — сколько байт уже отправлено -- При получении нового чанка: читается файл, отправляется отставание (до 1KB за раз) -- При ошибке отправки — чанк пропускается, ретрай на следующем чанке или в BLOCK_DONE -- Бэкпрессур через `etcp_router_on_send_ready` для асинхронной досылки +Каждый BLOCK_REQ имеет новый случайный `transfer_id[16]`. BLOCK_CHUNK, BLOCK_DONE, +BLOCK_OVERLOADED, BLOCK_RELAY_FULL и CANCEL повторяют его. Группа и end-to-end +отправитель берутся из router. Получатель проверяет группу, текущего держателя, +media_id, block_id, chunk, состояние REQ/RECV и UUID до файловых операций. +Поздние ответы прежней попытки не меняют таймеры, файл и счётчики новой. -### Переполнение relay (RELAY_FULL) -- Максимум 5 downstream-узлов на блок (`MD_MAX_RELAY_DOWNSTREAM`) -- При переполнении B отправляет C пакет `RELAY_FULL` (subcmd 0x11) - со списком node_id текущих downstream-узлов -- C извлекает список и пробует запросить блок у одного из этих узлов +CHUNK содержит ровно объявленное число байтов, от 1 до 1024. Принимается только +непрерывный префикс в пределах ожидаемого размера блока. Полный дубль игнорируется +без обновления прогресса; разрыв, частичное перекрытие и повреждённая текущая попытка +вызывают смену держателя. Ошибка локального чтения/записи/close завершает загрузку. +Прогресс публикуется после успешного закрытия записанного файла. -### Структуры данных +Каждая попытка создаёт уникальный temp и `media_block_source`. Producer загрузки +удерживает одну ссылку, каждая выдача — ещё одну. Источник индексирован по block_id; +группа, media_id и chunk дополнительно проверяются при выборе. Доступны только +успешно записанные байты `available`. Источник готового файла использует тот же +объект, но задаёт диапазон `base/size` и не владеет удалением файла. -**relay_block_ctx** — контекст relay для одного блока (индекс по block_id в `md->relay_blocks`): -- `block_id[16]`, `media_id[16]`, `chunk` — идентификаторы блока -- `chunk_file[2048]` — путь к `.chunk_N` файлу (источник данных для форвардинга) -- `file_offset` — всего получено байт от апстрима (размер файла) -- `downstream[5]` — массив relay_downstream +Один `stream_ctx` на получателя хранит свой UUID, offset, waiter, отложенный pump и +таймер простоя. Единственный pump выдаёт готовые диапазоны и растущие temp. +Waiter только планирует pump: синхронное срабатывание не создаёт рекурсивную отправку. +DATA и DONE соблюдают backpressure router. DONE со статусом OK отправляется, только +когда блок проверен и весь его хвост принят в очередь router. -**relay_downstream** — один пир, запросивший relay: -- `node_id`, `group_id` — кому слать -- `sent_offset` — сколько байт уже отправлено этому пиру -- `waiter` — handle для бэкпрессур-досылки +DONE не передаёт подпись: ожидаемая Ed25519-подпись взята из метаданных сообщения, +ровно 32 байта ключа автора копируются из nodes при старте. Проверяются размер файла +и подпись фактических байтов. Затем все блоки собираются в соседний temp; полный +SHA-256 проверяется всегда. После проверенных файловых операций имя назначения +заменяется атомарно, затем регистрируется в SQLite. Между ФС и SQLite общей транзакции +нет: ошибка регистрации оставляет опубликованный файл в учёте подготовки для очистки. -### Диаграмма relay-потока +До трёх выдач индексированных файлов допускаются одновременно; действует также +настраиваемый лимит на файл. У растущего блока до пяти relay-читателей. При перегрузке +RELAY_FULL содержит уникальных получателей именно этого блока своей группы, без +самого запросившего узла; если таких нет, возвращается OVERLOADED с задержкой. +CANCEL закрывает только точную выдачу данного узла/группы/UUID, снимая её счётчики один раз. -``` -[Узел A: источник] [Узел B: relay] [Узел C: req] - | | | - | |<── BLOCK_REQ ────────────| - | |── relay downstream add | - | |── catchup send (если есть)| - | | | - |── BLOCK_CHUNK ──────────>| | - | |── fwrite (в файл) | - | |── fread → BLOCK_CHUNK ──>| - | | | - |── BLOCK_DONE ───────────>| | - | |── fwd BLOCK_DONE ───────>| - | |── md_relay_remove | -``` +После завершения producer проверенный temp остаётся до окончания последнего читателя. +Отмена непроверенного producer помечает источник ошибочным и будит выдачи для ответа +UNAVAILABLE; при недоступной очереди выдача освобождается по таймауту. +Удаление канала отменяет его загрузки и выдачи. Waiter, pump и timer снимаются до free. -### Жизненный цикл relay_block_ctx +Отмена из progress callback немедленно отсоединяет загрузку, но обработчик удерживает +её память до возврата из callback. Только после снятия последней ссылки вызывается +done. Повторный start того же media_id независим от старого контекста и его пакетов. +Входящие и исходящие писатели используют общую резервацию нормализованного назначения. -1. **Создание** — в `md_dl_start_block` при отправке первого BLOCK_REQ -2. **Обновление file_offset** — в `media_download_handle_chunk` при каждом чанке -3. **Форвардинг downstream** — в `media_download_handle_chunk` после записи -4. **Завершение** — в `media_download_handle_done` после валидации: форвард BLOCK_DONE + удаление -5. **Отмена** — при `media_download_cancel`: контекст удаляется вместе с `md->relay_blocks` +| Структура | Размер заголовка/пакета, байт | +|-----------|-------------------------------| +| BLOCK_REQ / CANCEL | 53 | +| BLOCK_CHUNK | 59 + data_len | +| BLOCK_DONE | 58 | +| BLOCK_OVERLOADED | 57 | +| BLOCK_RELAY_FULL | 55 + num_relay_nodes × 8 | -### Константы relay - -| Константа | Значение | Описание | -|-----------|----------|----------| -| `MD_MAX_RELAY_DOWNSTREAM` | 5 | Макс число downstream-узлов на блок | -| `MEDIA_RELAY_FULL_HDR_SIZE` | 39 | Размер заголовка RELAY_FULL (1+16+16+4+2) | +Регрессии: `tests/test_media_blocks.c` проверяет I/O, реальные подписи с первым байтом +ключа 00, отмену/повторный start, поздние пакеты, лимиты и две relay-очереди разной скорости. +`tests/test_media_delivery_full.c` проверяет сетевую загрузку усечённого последнего блока.