diff --git a/doc/etcp_multipath_plan.txt b/doc/etcp_multipath_plan.txt index ed814efb..5f0b2ed8 100644 --- a/doc/etcp_multipath_plan.txt +++ b/doc/etcp_multipath_plan.txt @@ -37,7 +37,12 @@ STARTUP/DRAIN, сериализация и обратный трафик не д 5. RTO общий, основан на RTT/jitter соединения. Очередь wait_ack просматривается как FIFO с единым timeout; индивидуальные дедлайны с такой остановкой обхода - несовместимы. Повтор может снова уйти в зависший канал. + несовместимы. Повтор может снова уйти в зависший канал. Приоритет следующего + канала сейчас не реализован: select_link не получает last_link пакета, + send_hist только записывается, а общий RR относится ко всему соединению. + Воспроизведение на настоящих очередях/RTO при двух готовых UP-каналах дало + seq1 A->A и seq2 B->B. Тест late_ack_and_migration этого не покрывает: + он явно отключает A перед передачей через B. 6. Поток собирается по общему seq: при RTT 20/200мс разность времени доставки неизбежно создаёт переупорядочивание. Исправление ACK не устраняет ожидание @@ -47,6 +52,13 @@ STARTUP/DRAIN, сериализация и обратный трафик не д 7. stcp_link_send() подтверждает постановку в tx_queue. Это не означает прогресс записи в TCP. Для stalled TCP одной проверки link_status/cwnd/pacer недостаточно. +8. В приёмнике ACK ставится в ack_q до выделения payload/ETCP_FRAGMENT и вставки + в recv_q. При последующем OOM или отказе вставки уже подготовленный ACK может + подтвердить несохранённые DATA. В новом контракте положительный ACK допустим + только после сохранения фрагмента либо проверки, что он уже сохранён/доставлен. + Инъекция отказа data_pool подтвердила: ACK_queued=1, DATA_stored=0, + delivered_till=0. Это дефект надёжности, а не только неточность статистики BBR. + Экспериментальная проверка причин Диагностический бинарник /tmp/etcp-rtt-experiment включает штатный multipath-стенд @@ -92,15 +104,32 @@ STARTUP/DRAIN, сериализация и обратный трафик не д - Логический объект: общий seq, payload, признак доставки. Он отвечает за порядок потока, unacked_bytes приложения и очередь повторной доставки. + Подтверждение фрагмента означает принятие его приёмником в надёжную очередь; + сборка и выдача приложению идут своим порядком. Переход в очередь повтора + идемпотентен: таймеры нескольких копий не создают несколько записей одного seq. + Первый положительный ACK или till, покрывающий этот seq, удаляет ожидающий повтор. - Попытка: уникальный uint64 attempt_id, ссылка на канал, seq, размер, время отправки, delivery snapshots и состояние учёта окна. Последующая отправка создаёт новую попытку; предыдущие снимки остаются неизменными. + Глобальные очереди содержат логические фрагменты: готовые к отправке и ожидающие + доставки. Отдельные записи попыток принадлежат очередям линков; один ll_entry + не включается в две очереди. Попытка хранит seq, а не висячую ссылку на уже + доставленный логический объект. - attempt_id монотонен в пределах согласованной ETCP-сессии и не переиспользуется при удалении/создании линка. Перезапуск счётчика допустим только с новой эпохой, когда DATA/ACK старой сессии уже отклоняются. Переполнение закрывает сессию. -- DATA содержит seq + attempt_id. ACK элемента содержит attempt_id и ограниченную - задержку получателя. Задержка передаётся без короткого modulo-wrap; переполнение - помечает измерение недействительным. Пересчитать DATA MSS, MTU и ACK batching. +- DATA содержит seq + attempt_id. ACK элемента сохраняет оба идентификатора: + seq:u32 + attempt_id:u64 + receiver_delay_us:u32. seq позволяет подтвердить + логическую доставку даже после удаления метаданных старой попытки, без отдельной + истории попыток. Для известного attempt_id проверять соответствие seq. + Задержка насыщается, UINT32_MAX означает недействительный RTT sample. + Пересчитать DATA MSS, MTU и ACK batching; не использовать modulo-wrap задержки. +- ACK означает принятие DATA в надёжную очередь приёмника, не приход в сокет. + Сначала проверить границы, сохранить DATA/вставить в recv_q либо найти дубликат, + затем поставить ACK и лишь потом вызывать сборку/пользовательские callbacks. + Это сохраняет порядок при reentrant close/reset. При OOM до сохранения DATA + положительного ACK нет. + При OOM самого ACK данные остаются сохранёнными; следующая копия получает ACK. - Подтверждение попытки возвращается через тот же ETCP_LINK, где она принята. Очередь ACK принадлежит линку; piggyback содержит только ACK этого линка. Дубликат логического DATA с новым attempt_id тоже подтверждается. @@ -111,15 +140,61 @@ STARTUP/DRAIN, сериализация и обратный трафик не д - Общий till остаётся подтверждением логической доставки и места приёмника. Он может прийти по любому живому пути, освобождает payload/очередь повторов, но не придумывает доставку попытки, RTT/BW или освобождение чужого окна. + Продвижение till должно пробуждать отправку отдельного ACK с count=0 через + готовый рабочий канал, даже без DATA и без ACK-элементов на нём. Объединять + продвижения и ограничивать частоту, не создавать ответ на каждый ACK. + Потеря последнего till не должна оставлять полное окно приёмника навсегда: + ожидание места имеет таймер запроса актуального till через рабочий путь, + получатель отвечает текущей границей. Таймер принадлежит соединению, активен + только при ожидающих DATA и полном окне, отменяется при прогрессе/close/reset. + Session-wrapped запрос/ответ не меняет модели BBR; частота ограничена. - Доставка приложения учитывается один раз. Физические попытки учитываются каждая в своей модели; их суммарная доставка может включать дубликаты DATA. - ACK или объявление потери освобождает окно попытки ровно один раз. Поздний ACK не дебетует его повторно. После доставки payload можно освободить, метаданные других попыток живут до ACK/потери/закрытия, без удержания лишнего payload. - История ограничена по числу и сроку; неизвестный старый ID безопасно игнорируется. + При потере запись попытки можно удалить: поздний ACK по seq ещё подтверждает + логическую доставку, но неизвестный attempt_id не обновляет BBR или окно. + Отдельная история/tombstone для уже потерянных попыток не нужна. - Удаление линка/сессии отменяет его таймеры, удаляет ACK и попытки, освобождает учёт ровно один раз. Не хранить указатель на уже освобождённый ETCP_LINK. +Правило повторной передачи, независимо от определения DOWN + +Повтор использует историю именно своего логического фрагмента, не last_rr_link +соединения и не позиции каналов в send_hist. При готовых A/B/C порядок попыток +одного фрагмента — A->B->C->A; отправки других фрагментов и ACK его не меняют. +Начать обход после last_link фрагмента, выбрать первый подходящий другой линк. +Пропускать DOWN, неготовые, не вмещающие DATA и заблокированные pacer/транспортом. +Если готовых альтернатив нет, разрешён готовый прежний линк. Если не готов никто, +фрагмент ждёт штатного события ACK/pacer/transport-ready, без busy loop и нового RTO +для ещё не отправленной копии. Временно занятый следующий линк не блокирует поиск +остальных; отправлять без cwnd/pacing или ждать неограниченно именно его нельзя. + +Для первоначальной отправки остаётся обычный выбор по загрузке. Retry — отдельный +режим той же функции выбора, с едиными проверками готовности, без второго набора +условий cwnd/pacing. Burst не навязывает DATA свой линк: при конфликте измерение +идёт с FILLER, повтор отправляется по своему правилу. Старый линк не объявляется +DOWN только потому, что копия отправлена другим путём. + +last_link логического фрагмента — только заимствованный курсор выбора. При удалении +линка очистить такие курсоры до free; при NULL начать с первого рабочего линка. +Переиспользуемый uint8 local_link_id и порядковый номер списка не являются +идентичностью попытки. send_count не должен определять retry через оборачиваемый +uint8: хранить факт состоявшейся отправки отдельно от статистики. +Курсор меняется после принятия новой попытки транспортом. Ошибка подготовки/OOM +или отказ отправки не должны создавать фиктивный flight и ждать сетевого RTO. + +Повтор и объявление потери — разные события. При ранней отправке второй копии +первая ещё может быть в пути: её окно и метаданные остаются до собственного ACK, +срока потери или закрытия. Сам по себе повтор не является loss sample для BBR. +Дополнительные копии ограничены общим бюджетом памяти и окнами их каналов. +Ротация сокращает число попыток через зависший путь, но не сокращает ожидание +первого таймера. Нельзя обещать паузу <=100мс только сменой канала при RTO>100мс. +Срок ранней повторной отправки и допустимую цену дубликатов проверять отдельно; +сохранять физически достижимые требования конкретных профилей, не обещать один +предел задержки для произвольных RTT, нагрузки и потерь. + Почему не общий самый быстрый обратный канал Такой выбор не согласует интервалы и не устраняет неоднозначность повторов. @@ -141,18 +216,31 @@ STARTUP/DRAIN, сериализация и обратный трафик не д Добавить индекс attempt_id в очереди попыток каждого линка и отдельную ACK-очередь. Общий индекс попыток не нужен: ACK ищется на принявшем его линке. Разделить обработчики logical till и ACK попытки; одновременно обновить DATA, - ACK, декодеры, MTU/MSS, lifecycle и .h. Не оставлять промежуточный формат, + ACK, декодеры, MTU/MSS, lifecycle и .h. В этом же изменении нужны очередь/таймер + попыток каждого линка и собственный RTT/RTO: иначе старый глобальный FIFO + перестаёт соответствовать новому владению. FIFO допустима при одном текущем + RTO для всех попыток линка; пересчитывать таймер при его смене. + До первого DATA ACK использовать RTT того же KA-пути как начальную оценку + либо консервативный начальный RTO; не начинать неизвестный путь с 5мс. + Добавить ротацию retry сразу, пока старый канал ещё UP; не откладывать до DOWN. + Не оставлять промежуточный формат, в котором новый ACK продолжает обращаться только к last_link. Проверки: первая копия A -> повтор B -> поздний ACK A; обратный порядок ACK; ACK дубликат; чужой/неизвестный ID; till с другого пути; link recreation; - потеря ACK при уже доставленном payload; close/reset с активными попытками. - После этого повторить всю матрицу скоростей. Балансировщик менять только + потеря ACK при уже доставленном payload; close/reset с активными попытками; + отказ принятия пакета транспортом без продвижения курсора и фиктивного flight; + OOM сохранения DATA, вставки в recv_q и создания ACK. Проверить передачу till + без DATA по другому пути и восстановление после потери последнего till. + Для retry: A->B при обоих UP и меньшем inflight у A; A->B->C->A; + посторонние DATA/ACK между попытками; пропуск заблокированных каналов; + один доступный путь; все временно заняты; burst старого пути; удаление курсора. + После этого повторить всю матрицу скоростей. Выбор пути первоначальных DATA менять только при сохраняющейся недогрузке: эксперименты уже показывают полную утилизацию без смены алгоритма выбора DATA, поэтому дополнительная эвристика пока лишняя. -3. Независимые сроки и восстановление каналов. - RTT/RTO и таймер старейшей попытки принадлежат каналу. FIFO допустима только - при одном текущем RTO для всех его попыток; пересчитывать таймер при его смене. +3. Быстрое восстановление и здоровье каналов. + Собственный RTT/RTO и ротация уже обязательны в этапе 2. Проверить срок первой + повторной отправки, отдельно от объявления потери/снятия окна старой попытки. При потере ограничить новые DATA на проблемном пути, пробовать восстановление ограниченными probe, повторять недоставленные DATA через рабочие каналы. Не отправлять следующий bulk вслепую в тот же застрявший путь. @@ -190,6 +278,9 @@ test_nat_detection_faults, test_nat_detection, test_encrypted_attachment. /tmp/etcp-rtt-settled-karn.log, /tmp/etcp-rtt-settled-karn-repeat.log, /tmp/etcp-rtt-settled-fast-distant.log, /tmp/etcp-rtt-settled-slow-distant.log, /tmp/etcp-rtt-original-karn.log. +Проверки отсутствующей ротации и ложного ACK при OOM: /tmp/etcp-retry-review.c, +/tmp/etcp-retry-review.mk, /tmp/etcp-retry-review.log +(exit=1: retry_rotation=2, false_ack=1, total=3; учёт/освобождение ресурсов проверены). Сборка диагностического бинарника: make -C tests -f Makefile -f /tmp/etcp-rtt-experiment.mk /tmp/etcp-rtt-experiment Основной повтор: diff --git a/doc/tasks.md b/doc/tasks.md index 5e2f6002..92fb36f0 100644 --- a/doc/tasks.md +++ b/doc/tasks.md @@ -45,8 +45,19 @@ Контроль прототипа по штатному rtt с прогревом 3с: 14.312/16Мбит/с, очередь 23.4/24.9мс, PASS. Это проверка причин, а не реализация новой архитектуры: нужны attempt_id, владение очередями и lifecycle. Начать с начального pacing, затем согласованно разделить логический seq и физические попытки/ACK, - после этого исправлять сроки/здоровье линка и DROP/HOLD. Смену DATA-балансировщика пока не навязывать: + после этого исправлять сроки/здоровье линка и DROP/HOLD. Выбор первоначального DATA-пути пока не менять: прототип уже утилизирует пути без неё. Штатные проверки разгона и отказов остаются обязательными. + Дополнительный архитектурный разбор: ротации повтора относительно пути пакета сейчас нет. + /tmp/etcp-retry-review.log: оба канала UP и готовы, seq1 A->A, seq2 B->B, retry_rotation=2. + В контракт добавлены A->B->C->A с пропуском занятых и возвратом на прежний только без готовых альтернатив; + burst не навязывает путь DATA. Ротация, очереди попыток и собственные RTO входят в единый этап перехода. + ACK сохраняет seq вместе с attempt_id: поздний ACK освобождает логические данные и после удаления попытки, + не меняя её уже снятое окно/BBR. Отдельная история потерянных попыток не требуется. + Найдено подтверждение DATA до успешного сохранения в recv_q: включить исправление admission/ACK и OOM-тесты. + Воспроизведение с отказом data_pool: ACK_queued=1, DATA_stored=0, delivered_till=0; false_ack=1. + Также нужны передача till без DATA по другому пути и запрос потерянной границы при полном окне. + Ранний повтор не равен loss: его старая копия остаётся в учёте до ACK/потери/close. + Одна ротация не обеспечивает паузу <=100мс, если срок первой повторной отправки больше 100мс. [+] **ETCP/BBR: бюджет очереди в установившемся режиме** — реализовано 23.10.2026 по выбору пользователя: В PROBE_BW окно ограничено max(4*MSS, BDP + BW*20мс), с насыщением арифметики и учётом ACK/PROBE_UP резервов