Document send scanning, retry rounds, and fast stall recovery

This commit is contained in:
evgeny committed 2026-10-24 12:01:20 +02:00
1 parent 93bf846fc4
commit 34d1dcb7c8
3 files changed
+29 -12

No files matched your search

+21 -7
View File
@@ -9,26 +9,35 @@ close/reset отменяет ожидающий запуск. Не подтве
HELLO/CHALLENGE/CONFIRM и keepalive передаются своим независимым путём.
Формирование пакета:
1. Посмотреть следующий DATA и его длину, не извлекая его из input_send_q.
1. Просмотреть input_send_q без извлечения: первый готовый повтор, иначе первый готовый новый DATA.
Заблокированная запись пропускается; порядок остальных записей не меняется. Поиск не резервирует окно,
не выдаёт номер попытки и не меняет историю/RR. Если кандидата нет, можно подгрузить один новый фрагмент
и повторить просмотр. Общий бюджет учитывает DATA-байты send_q+wait_ack; приёмное окно по till сохраняется.
После callback подгрузки проверяются reset_count/close/сессия, включая удаление прежнего burst-линка.
2. Выбрать инициализированный UP-линк с готовым shaper и местом для целого DATA:
inflight_bytes + длина DATA <= inflight_lim_bytes. Проверка выполняется без переполнения.
Среди допустимых выбирается минимум inflight_bytes, при равенстве — round-robin.
Для повтора обход начинается после last_link самого фрагмента: первый готовый другой канал.
Пока существует другой initialized+UP канал, прежний не используется: занятость альтернатив
по cwnd/pacing означает ожидание их события без создания новой попытки/RTO.
Прежний канал разрешён только при отсутствии таких альтернатив; DOWN последней альтернативы
пробуждает отправителя. Чужие DATA/ACK не меняют курсор фрагмента; удаление линка обнуляет его до free.
Для повтора обход начинается после last_link самого фрагмента: сначала неиспользованные допустимые пути.
История — битовая карта256 локальных слотов,32 байта на логический пакет; wire-формат не меняется.
Пока остался неиспользованный путь, занятый по cwnd/pacing, этот пакет ждёт, а send_q просматривается дальше.
Когда все допустимые пути использованы, разрешён следующий обход. Очистка истории и новый бит фиксируются
только после принятия send; отказ send не расходует обход. Перед удалением линка очищаются его курсор/бит
во всех логических записях до освобождения слота и callbacks, которые могут повторно выдать тот же local_link_id.
При наличии незастрявшего initialized+UP пути застрявшие исключены из повторов, даже если альтернатива занята.
Если застряли все, доступны все UP-пути. Прежний путь разрешён лишь при отсутствии других допустимых;
иначе ждём альтернативу. DOWN последней альтернативы пробуждает отправителя.
3. Если DATA не помещается, он остаётся в очереди; для накопленных ACK выбирается
тот же линк, который принял соответствующий DATA, без ограничения DATA-cwnd/pacing.
Кумулятивный till можно отправить по любому рабочему пути. ACK-only не увеличивает нагрузку DATA-shaper;
физическая очередь канала остаётся общей, её накопление ограничивают DATA-cwnd/pacing BBR.
4. Выделить ETCP_DGRAM до удаления DATA из очереди и изменения счётчиков.
Выделить физическую попытку с новым attempt_no для этого seq и зарезервировать окно выбранного линка.
Только успешное принятие транспортом фиксирует время, last_link и перенос DATA в input_wait_ack.
Только успешное принятие транспортом фиксирует время, last_link, историю/RR и перенос DATA в input_wait_ack.
Ошибка send снимает резерв; логический DATA остаётся готовым без сетевого loss sample.
Пакет содержит ACK, опциональные timestamp/измерительные секции и последнюю секцию PAYLOAD.
5. В активной ограниченной burst-серии pacing обходится; DATA всё равно должен
помещаться в окно, иначе серия отправляет FILLER. ACK/FILLER окно DATA не занимают.
Если готовый повтор относится к другому пути, серия идёт FILLER: новые DATA его не обгоняют.
При приёме пакета (от etcp_connections):
- последовательно сканируем секции и отдаём их на обработку нужным обработчикам:
- ack (0x01): пара seq+attempt_no подтверждает физическую попытку только принявшего ACK линка;
@@ -113,6 +122,11 @@ Shaper списывает только DATA-байты: единицы совп
не должно скрывать этот факт от BBR. app_limited учитывает ожидающие очереди и
pending/частичный буфер normalizer; ограничение окном не означает отсутствие данных.
Потеря для BBR учитывается при RTO, фактическая повторная передача — при новой попытке.
Три RTO физических DATA-попыток без ACK-прогресса ставят локальный флаг tx_stalled, в том числе в одном вызове таймера.
До установки флага действующий ACK своего пути сбрасывает серию RTO. После установки сброс возможен только по ACK
попытки, принятой транспортом после момента установки флага; старый/чужой/уже снятый attempt не доказывает восстановление.
tx_stalled ограничивает выбор повторов при наличии альтернативы; UP, новые DATA, ACK/keepalive и формула RTO сохраняются.
Reset линка снимает флаг и серию RTO. Новых очередей, копий payload и таймеров детектор не создаёт.
При уменьшении окна уже отправленные пакеты остаются в учёте; новые DATA ждут ACK/RTO.
Измерительные секции burst 0x07/0x08 и FILLER поддерживаются реализацией.
+7 -4
View File
@@ -152,7 +152,7 @@ struct ETCP_CONN {
struct memory_pool* attempt_pool; // метаданные физических попыток; payload принадлежат inflight_pool
struct memory_pool* io_pool; // память для rx очередей
struct ll_queue* input_send_q; // очередь на отправку (inflight_pool -> INFLIGHT_PACKET)
struct ll_queue* input_send_q; // новые DATA/повторы; выбирается готовый повтор либо готовый новый пакет, не только голова
struct ll_queue* input_wait_ack; // очередь ожидающих подтверждение (inflight_pool -> struct INFLIGHT_PACKET)
struct ll_queue* recv_q; // очередь на сборку фрагментированных пакетов(rx_pool -> struct ETCP_FRAGMENT) + index [+0 SEQ]
@@ -192,9 +192,9 @@ struct ETCP_CONN {
// uint32_t bytes_sent_norx; // сколько отправили байт без единого ответного пакета (для детекции запроса реконнекта)
// Window and inflight management
uint32_t unacked_bytes; // Current inflight bytes
uint32_t unacked_bytes; // логические DATA-байты input_send_q + input_wait_ack; не физический flight копий
uint32_t max_inflight; // Max inflight cap from config (bbr_max_cwnd)
uint32_t optimal_inflight; // Sum over links
uint32_t optimal_inflight; // сумма индивидуальных окон; общий бюджет подгрузки новых DATA с учётом unacked_bytes
// Timers
void* ack_resp_timer; // ACK send timer
@@ -341,7 +341,10 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt);
// Returns: 0 on success, -1 on failure
int etcp_int_send(struct ETCP_CONN* etcp, const void* data, uint16_t len);
// Request next packet for load balancer
/* Подготовить следующий пакет из pkt_pool либо NULL при ожидании/ошибке/отсутствии работы.
* Сканирует send_q: готовые повторы имеют приоритет, заблокированные записи пропускаются.
* При отсутствии кандидата допускает одну подгрузку в рамках общего/приёмного окна; callback может сделать reset/close.
* Пакет с DATA содержит резерв попытки: etcp_loadbalancer_send завершает send/rollback и возвращает пакет в пул. */
struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp);
/* Запросить один отложенный проход отправителя; не меняет UP/DOWN. Все вызовы — в uasync.
+1 -1
View File
@@ -58,7 +58,7 @@ struct ETCP_TX_ATTEMPT* etcp_tx_prepare(struct ETCP_LINK* link, struct INFLIGHT_
void etcp_tx_complete(struct ETCP_DGRAM* dgram, int success);
/* Принятый ACK: seq подтверждает логическую доставку; внутренний attempt_id=(seq<<16)|номер обновляет только свой линк.
* Неизвестный старый ID не создаёт измерение и не вычитает окно. UINT32_MAX delay — RTT недостоверен.
* Действующий ACK сбрасывает серию RTO; флаг застревания снимается только для отправки после его установки.
* До застревания действующий ACK сбрасывает серию RTO; после — только ACK отправки после установки флага сбрасывает серию/флаг.
* peer_rx_bytes/time_us — согласованная точка приёма пути из ACK; 0/0 — неизвестна.
* Достоверная пара ограничивает BW темпом приёма, сохраняя границы send/ACK; RTT и доставка от неё не зависят.
* interval=max(send_us,ack_us,ceil(delivered*delta_rx_us/delta_rx_bytes)); дельты от снимка при отправке.