From 9a648219cf6091923d41016f6bd03642e953adb6 Mon Sep 17 00:00:00 2001 From: evgeny Date: Sun, 11 Oct 2026 15:04:58 +0200 Subject: [PATCH] Document automatic stream selection, wire v3, active-mode ownership and verified behavior --- doc/stream.txt | 69 ++++++++++++++++++++++++++++++++++++++++---------- doc/tasks.md | 13 +++++++++- 2 files changed, 67 insertions(+), 15 deletions(-) diff --git a/doc/stream.txt b/doc/stream.txt index 993efb91..d63ea0a3 100644 --- a/doc/stream.txt +++ b/doc/stream.txt @@ -220,6 +220,10 @@ RENEW создаёт только продление существующей в ACCEPT содержит handle, фактический начальный индекс, границы кольца и путь субпотока. Реальное получение блока, а не только ACCEPT, подтверждает пригодность выдачи. +Индекс сразу после последнего доступного блока разрешён: курсор ждёт его появления в том же кольце. +Более далёкий будущий индекс отвергается. Пустой источник допускает выдачу с 0/с конца, +которая ждёт первый блок; первый проверенный DATA по-прежнему устанавливает общий N. +Пустой ретранслятор пути не объявляет. Завершение без данных подтверждается подписанным END с last_seq=UINT64_MAX. Отказы различают отсутствие подписки/трансляции, эпоху, FEC, позицию, слот, перегрузку, цикл и право доступа. DATA содержит поколение сессии, handle, неизменяемый подписанный блок источника и изменяемый путь. Источник подписывает и END с ключом трансляции и индексом последней пачки, чтобы ретранслятор не мог подделать окончание. @@ -311,6 +315,8 @@ INFO_RESP также сообщает число свободных исходя Начальный поиск учитывает источник. Фоновые пробы ретрансляторов исключают мобильных. Мобильный узел может быть источником и получателем; выбранный мобильный источник допускается к подключению. Активное получение/публикация согласовывается с активностью ядра, чтобы standby не прерывал поток. +Локальные owners удерживают эффективный ACTIVE до последнего close, включая сохранённый буфер после END. +Запрошенный frontend режим хранится отдельно: STANDBY не отменяет стрим, а применяется после снятия последнего владения. При запуске экземпляра одновременно пробуем до 32 совместимых по типу подключения кандидатов. Если подходящих меньше, пробуем имеющихся. Это узлы, а не 32 отдельных адреса или субпотока. @@ -578,21 +584,49 @@ Stop/destroy выполняются по правилам ядра, вне callb Обычная сборка восстановлена; ./check.sh: 144 passed, 0 failed, 0 skipped; proxy/full (9/9), burst и load — PASS. Лог: /tmp/stream-pressure-check.log (90 с). -[ ] Оптимизация сети и интеграционные проверки: - пути/защита от циклов, стартовые 32 пробы, дальнейший темп 1/с и полный цикл не чаще 30 с, - постепенный перенос с перекрытием, failover, несколько источников и независимое закрытие на общем ETCP. - -Сетевая выдача подключена: stream_receive_open выбирает явного пира/субпоток/позицию, -stream_receive_get_info сообщает handle/прогресс/причину, stream_receive_close снимает только свою выдачу. -Локальная подписка по-прежнему владеет кольцом; receive handle её не заменяет и закрывается отдельно. -Прямой dispatcher в старой части теста сохранён для изолированной проверки FEC и закрытия из callback; -новая часть проверяет реальные сетевые bindings и передачу через relay. -Следующий этап — автоматические пробы 32/1с/30с, выбор поставщиков, перенос с перекрытием и failover. +[+] Автоматический выбор и перенос поставщиков — 11.10.2026: + stream_optimizer.c/h — один планировщик на локальный экземпляр, общий для всех owners подписки. + stream_subscribe_open автоматически запускает получение; stream_subscribe_manual_open оставляет ручные выдачи для диагностики. + Режимы не смешиваются; внешний receive/probe/restart не может вмешаться в автоматическое владение handles. + INFO-only probe не резервирует SUBSCRIBE handle и не передаёт DATA; одним NCD-владением последовательно опрашивает субпотоки. + Общий stream_peer переиспользует NCD между группами/источниками; CLOSED handle переоткрывается перед новой попыткой. + Совместимость проверяется в NCD без создания линков: общий исходящий обход IPv4/IPv6/TCP, + возможность reverse по публикуемым адресам и sock_match; фактическую достижимость подтверждает ограниченная проба. + Стартовая серия — до 32 узлов; далее 1 новый узел/с. Посещённые узлы не повторяются в цикле, + новый цикл ждёт завершения обхода/активных проб и минимум 30с; новые кандидаты добавляются без нового burst. + Неуспехи имеют backoff 30/60/120/240/480с; подтверждённая проба восстанавливает доступность. + INFO сохраняет диапазон, фактический путь, metrics, RTT, свободные slots, accepting, queue, CPU и возраст прогресса. + RTT цепочки относится к этому пути; смена фактического пути DATA делает старое измерение неизвестным, + новая проба обновляет RTT соответствующей активной выдачи. Невыбранные снимки истекают через 60с. + Первый поставщик запрашивается с конца; остальные стартуют с его проверенного N, даже если k уже позволили выдать пачку. + Поиск учитывает доступную старую позицию, запрет LOOP, проблемы/RTT, лимит r при наличии альтернатив и общие узлы пути. + Более новый индекс повторно проверяется свежим INFO/SUBSCRIBE: старое объявление last не является вечным верхним пределом live-потока. + Обычно переносится один субпоток; новая выдача перекрывает старую и заменяет её только после проверенного DATA и догоняющего cursor. + По умолчанию: подтверждение улучшения 2с, удержание поставщика 5с, RTT-гистерезис 20мс; параметры доступны через stream_selection_configure. + Новая выдача без достаточного продвижения отменяется через 5с, старая остаётся. CLOSED/DOWN восстанавливают несколько отсутствующих субпотоков. + При отсутствии decode-прогресса 5с, недостатке работающих субпотоков и наличии более новой истории возможен явный restart с конца; + пропущенный диапазон передаётся потребителю GAP, кольцо/downstream сохраняются. Сам по себе простой источника restart не вызывает. + stream_selection_get_info сообщает пробы/циклы, выдачи, перекрытия, поставщиков, миграции, отказы/restart и фактическую FEC-защиту. + Защита подтверждается всеми n проверенными работающими субпотоками и не более r на поставщика; при недостатке поставщиков помечается ослабленной. + Владельцы публикации/подписки удерживают ACTIVE; запрошенный STANDBY восстанавливается после закрытия последнего экземпляра. + Подписанный END проходит также для источника без единого DATA. + test_stream_core проверяет границы 32/1с/30с, новые записи, долгий/незавершённый обход и отсутствие повторного burst. + test_stream_service: INFO без DATA, пустой источник, общие owners, цикл 30с, 400 непрерывных пачек, + распределение FEC, перенос с перекрытием, остановка relay, реальный CLOSED, сохранение ETCP, END без данных и восстановление STANDBY. + NCD-тест проверяет прямую/reverse-only/несовместимую пару без изменения соединений. + stream core/service, NCD и standby transport прошли ASAN/LeakSanitizer без ошибок и утечек. + Логи: /tmp/stream-optimizer-asan-core.log, /tmp/stream-optimizer-asan-service.log, + /tmp/stream-optimizer-asan-ncd.log и /tmp/stream-optimizer-asan-standby.log. + +[ ] Расширенные сетевые проверки: + реальные NAT/direct/reverse, большой набор поставщиков, смена адресов и мультилинк под длительной нагрузкой, + одновременная перестройка нескольких ретрансляторов, нагрузка нескольких автоматических источников/групп и mobile standby на устройстве. + Живой стенд проверяет несколько поставщиков на четырёх ядрах; граница 32 и сроки отдельно проверяются чистой политикой расписания. 17. Зафиксированный сетевой формат и пределы реализации Все stream-числа на wire — big-endian, без packed-структур и зависимости от host byte order. -Версия stream wire — 2; обратная совместимость не поддерживается. +Версия stream wire — 3; обратная совместимость не поддерживается. Заголовок raw (52 байта): cmd:1, version:1, command:1, subblock:1, group_id:8, source_node_id:8, source_epoch:8, sender_epoch:8, receiver_epoch:8, handle:8. INFO_REQ/RESP согласуют sender/receiver epochs; неизвестная receiver_epoch=0 допустима только при начальном INFO_REQ/reverse. @@ -602,15 +636,20 @@ sender берётся из end-to-end router metadata. DATA через routed se INFO_REQ: probe_nonce:8, не резервирует номер SUBSCRIBE. INFO_RESP/ACCEPT: probe_nonce:8 (0 в ACCEPT), actual_seq:8, first_available:8, last_available:8, n:1, r:1, block_size:2, metric_flags:1, lag_ms:8, source_rate:8, receive_rate:8, blocks:8, late_blocks:8, stops:8, -path_count:1, node_ids:8*path_count — фиксированная часть 86 байт. В INFO actual_seq — последний доступный, в ACCEPT — старт. +offer_flags:1, free_slots:2, cpu_basis_points:2, queue_bytes:8, progress_age_ms:8, path_rtt_ms:4, +path_count:1, node_ids:8*path_count — фиксированная часть 111 байт. В INFO actual_seq — последний доступный, в ACCEPT — старт. metric_flags: bit0 — измерение известно, bit1 — source_rate известен, bit2 — receive_rate известен, bit3 — stopped. +offer_flags: bit0 — принимает новые выдачи, bit1 — RTT цепочки известен, bit2 — возраст прогресса известен, +bit3 — CPU известен, bit4 — кольцо непусто для этого субпотока. CPU — 0..10000 (сотые доли процента). +Пустой источник: first/last=UINT64_MAX, bit4=0, INFO actual_seq=UINT64_MAX, ACCEPT actual_seq=0, путь {source}. +Курсор last+1 допустим только в ACCEPT непустого кольца; DATA/END проверяются по точной позиции и подписи источника. SUBSCRIBE: requested_seq:8; REJECT: reason:1, для SUBSCRIBE дополнительно first_available:8,last_available:8, для INFO дополнительно probe_nonce:8. Старый nonce не подтверждает новую пробу. RENEW: один header handle без payload (каждая кодограмма продлевает один элемент списка). UNSUBSCRIBE/CLOSED: reason:1. DATA: path_count:1,node_ids:8*path_count,неизменяемый подписанный блок. GAP: old_cursor:8,next_cursor:8, уведомление включает диапазон [old_cursor,next_cursor). END: "stream_end":10,group_id:8,source_node_id:8,source_epoch:8,last_seq:8,Ed25519:64 — всего 106 байт. -UINT64_MAX в END означает пустой источник; запрос с конца пустого кольца явно отвергается (нет первого блока). +UINT64_MAX в END означает пустой источник; принимается только выдачей с next=0 и без ранее принятых DATA. OVERLOAD: request_id:8, required_rate:8 (байт/с), estimated_count:1, candidate_count:1, {handle:8,frozen_rate:8}*candidate_count; не более 64 элементов. OVERLOAD_SELECT: request_id:8, selected_count:1, handles:8*selected_count; не более 64 элементов. @@ -625,4 +664,6 @@ OVERLOAD_SELECT: request_id:8, selected_count:1, handles:8*selected_count; не отправка по освобождению до пустого порога; курсоры downstream обслуживаются round-robin. Для временного индекса каждый фактический блок дополнительно имеет seq/received_ms и два uint32 указателя списка субпотока. Логический Q не выделяет память под копии/задания на весь backlog и ограничен доступными блоками колец и числом выдач. -Эти пределы задают ограниченную память выдачи и разгрузки; автоматический выбор/перекрытие поставщиков пока не запускаются. +Эти пределы общие для ручных выдач, INFO-only проб и автоматических перекрытий; исчерпание не обходится планировщиком. +Снимки кандидатов — отдельно от DATA-кольца, по n субпотоков только у опрошенных узлов; +невыбранные снимки освобождаются через 60с (либо после действующего backoff), не создают дополнительных DATA-буферов. diff --git a/doc/tasks.md b/doc/tasks.md index 26957b28..bf0e1129 100644 --- a/doc/tasks.md +++ b/doc/tasks.md @@ -35,7 +35,18 @@ Обычная сборка восстановлена; ./check.sh: 144 passed, 0 failed, 0 skipped; proxy/full (9/9), burst и load — PASS. Лог: /tmp/stream-pressure-check.log (90 с). Wire версии 2 и пределы обновлены в doc/stream.txt, раздел 17. - Следующий этап — автоматические пробы 32/1с/30с, перенос с перекрытием и failover. + 11.10.2026 подключён автоматический планировщик: stream_subscribe_open начинает получение, + ручной режим выделен в stream_subscribe_manual_open. INFO-only пробы, цикл 32/1с/30с, + проверенные пути/RTT/метрики/слоты, backoff, распределение FEC, перенос с перекрытием и failover. + Первый DATA задаёт общий N; пустой источник и курсор last+1 ждут данные, END без DATA также подписан и доставляется. + Сохранён желаемый режим frontend; stream owners удерживают ACTIVE до close, затем восстанавливается STANDBY. + Статус/настройки — stream_selection_get_info/configure; wire версии 3 и пределы — doc/stream.txt, раздел 17. + На четырёх ядрах проверены 400 непрерывных пачек, общий планировщик владельцев, цикл 30с, + FEC spread, overlap до проверенного DATA, остановка relay/CLOSED и восстановление без разрыва ETCP. + Core/service, NCD и standby transport прошли ASAN/LeakSanitizer; остаток аллокаций — ноль. + Логи: /tmp/stream-optimizer-asan-{core,service,ncd,standby}.log. + Следующий этап — длительные/масштабные сетевые проверки: NAT/reverse, мультилинк, + одновременная перестройка, несколько автоматических источников/групп и mobile standby на устройстве. [+] **test_reality_tls: коллизии порта слушателя** — 10.10.2026: В общем прогоне 140 passed, 1 failed: stcp_server_create не смог занять порт 34922, bind errno=98 (EADDRINUSE).