diff --git a/doc/etcp_protocol.txt b/doc/etcp_protocol.txt index 88a68ea4..7c061f14 100644 --- a/doc/etcp_protocol.txt +++ b/doc/etcp_protocol.txt @@ -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 поддерживаются реализацией. diff --git a/src/transport_layer/etcp.h b/src/transport_layer/etcp.h index 4ff1ea63..92b28dd0 100644 --- a/src/transport_layer/etcp.h +++ b/src/transport_layer/etcp.h @@ -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. diff --git a/src/transport_layer/etcp_tx.h b/src/transport_layer/etcp_tx.h index 3c35825a..169cd7ac 100644 --- a/src/transport_layer/etcp_tx.h +++ b/src/transport_layer/etcp_tx.h @@ -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)); дельты от снимка при отправке.