Specify retry path rotation and close ACK ownership gaps

This commit is contained in:
evgeny committed 2026-10-23 02:08:04 +02:00
1 parent 259dfa35d9
commit 4aae1f6151
2 files changed
+114 -12

No files matched your search

+102 -11
View File
@@ -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
Основной повтор:
+12 -1
View File
@@ -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 резервов