From 9dbef48eb3f4f562aebb59b74d330017d88480d2 Mon Sep 17 00:00:00 2001 From: evgeny Date: Fri, 23 Oct 2026 12:52:35 +0200 Subject: [PATCH] Use RTT jitter and fixed margin for ETCP retransmission timeout --- src/transport_layer/etcp_tx.c | 2 +- src/transport_layer/etcp_tx.h | 3 ++- tests/test_etcp_windows.c | 33 ++++++++++++++++++++------------- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/transport_layer/etcp_tx.c b/src/transport_layer/etcp_tx.c index 975f7291..9c4855b2 100644 --- a/src/transport_layer/etcp_tx.c +++ b/src/transport_layer/etcp_tx.c @@ -16,7 +16,7 @@ static struct INFLIGHT_PACKET* logical_find(struct ETCP_CONN* conn, uint32_t seq uint32_t etcp_tx_rto_us(const struct ETCP_LINK* link) { uint64_t rtt = link->data_rtt_us ? link->data_rtt_us : (uint64_t)link->rtt_last * 100; - uint64_t rto = rtt ? 2 * (rtt + link->data_jitter_us) : 1000000; + uint64_t rto = rtt ? (12 * rtt + 15ULL * link->data_jitter_us + 9) / 10 + 3000 : 1000000; if (rto < 10000) rto = 10000; if (rto > 1000000) rto = 1000000; return (uint32_t)rto; diff --git a/src/transport_layer/etcp_tx.h b/src/transport_layer/etcp_tx.h index d99c93c9..c5688a93 100644 --- a/src/transport_layer/etcp_tx.h +++ b/src/transport_layer/etcp_tx.h @@ -33,7 +33,8 @@ int etcp_tx_link_init(struct ETCP_LINK* link); * reset не меняет физический UP; destroy дополнительно освобождает очереди. */ void etcp_tx_link_reset(struct ETCP_LINK* link, int retry); void etcp_tx_link_destroy(struct ETCP_LINK* link); -/* Расчётный RTO в микросекундах: 2*(собственный RTT+jitter), 10мс..1с; до DATA ACK использует KA RTT/1с. */ +/* RTO = clamp(ceil(1.2*RTT + 1.5*jitter + 3000), 10000, 1000000), все величины в мкс. + * RTT/jitter — собственные DATA-измерения линка; до DATA ACK использует KA RTT, без RTT — 1с. */ uint32_t etcp_tx_rto_us(const struct ETCP_LINK* link); /* Резервировать окно для packet; NULL — ошибка с логом, packet остаётся в очереди отправки. * Физические записи ограничены MAX_INFLIGHT_SIZE на линк и 2*MAX_INFLIGHT_SIZE на соединение. diff --git a/tests/test_etcp_windows.c b/tests/test_etcp_windows.c index d75a73cf..2b281cd9 100644 --- a/tests/test_etcp_windows.c +++ b/tests/test_etcp_windows.c @@ -329,27 +329,34 @@ static void shaper_ready(void* arg) { loadbalancer_link_ready(link); } -/* Реальный RTO: нижняя/верхняя границы и влияние RTT/jitter, без подмены обработчика таймера. */ +/* Реальный RTO: границы, округление, начальный jitter и выбор DATA/KA RTT; без подмены обработчика таймера. */ static void retransmission_timeout(void) { - const struct { uint16_t rtt, jitter; uint32_t expected_tb; } cases[] = { - {0, 0, 10000}, {20, 10, 100}, {200, 0, 400}, {200, 75, 550}, - {2000, 100, 4200}, {6000, 1000, 10000}, {UINT16_MAX, UINT16_MAX, 10000} + const struct { uint32_t rtt_us, jitter_us; uint16_t ka_rtt_tb; uint32_t expected_us; } cases[] = { + {0, 0, 0, 1000000}, {0, UINT32_MAX, 0, 1000000}, {2000, 1000, 0, 10000}, + {20000, 0, 0, 27000}, {20000, 7500, 0, 38250}, {200000, 10000, 0, 258000}, + {600000, 100000, 0, 873000}, {1000000, 0, 0, 1000000}, {UINT32_MAX, UINT32_MAX, 0, 1000000}, + {5833, 0, 0, 10000}, {5834, 0, 0, 10001}, {20001, 1, 0, 27003}, + {20000, 10000, 0, 42000}, {200000, 100000, 0, 393000}, + {0, 0, 200, 27000}, {20000, 0, 6000, 27000}, {0, 0, UINT16_MAX, 1000000}, + {830832, 0, 0, 999999}, {830833, 0, 0, 1000000}, {830834, 0, 0, 1000000} }; for (unsigned i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) { setup(); b->link_status = 0; - conn->rtt_avg_10 = cases[i].rtt; conn->jitter = cases[i].jitter; - a->rtt_last = 0; a->data_rtt_us = cases[i].rtt * 100; a->data_jitter_us = cases[i].jitter * 100; + conn->rtt_avg_10 = UINT16_MAX; conn->jitter = UINT16_MAX; // Общие/чужие метрики не участвуют в RTO этого пути. + a->rtt_last = cases[i].ka_rtt_tb; a->data_rtt_us = cases[i].rtt_us; a->data_jitter_us = cases[i].jitter_us; + CHECK(etcp_tx_rto_us(a) == cases[i].expected_us); fill(a, 1, 100); struct INFLIGHT_PACKET* packet = (struct INFLIGHT_PACKET*)conn->input_wait_ack->head; - uint64_t sent = packet->last_timestamp, deadline = sent + cases[i].expected_tb + 1000; + uint64_t sent = last_attempt(packet)->sent_at_us, deadline = sent + cases[i].expected_us + 100000; + unsigned expected_tb = (cases[i].expected_us + 99) / 100; // Блокируем только новую передачу: статус UP и настоящий retrans-таймер сохраняются. - a->shaper_timer = uasync_set_timeout(ua, cases[i].expected_tb + 10000, a, shaper_ready, "test_rto_shaper"); + a->shaper_timer = uasync_set_timeout(ua, expected_tb + 10000, a, shaper_ready, "test_rto_shaper"); CHECK(a->shaper_timer); - while (packet->state == INFLIGHT_STATE_WAIT_ACK && get_time_tb() < deadline) test_clock_poll(ua, 10); - uint64_t elapsed = get_time_tb() - sent; - DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "RTO: RTT_tb=%u jitter_tb=%u expected_tb=%u observed_tb=%llu state=%u", - cases[i].rtt, cases[i].jitter, cases[i].expected_tb, (unsigned long long)elapsed, packet->state); - CHECK(packet->state == INFLIGHT_STATE_WAIT_SEND && elapsed >= cases[i].expected_tb && elapsed <= cases[i].expected_tb + 1000); + while (packet->state == INFLIGHT_STATE_WAIT_ACK && get_time_us() < deadline) test_clock_poll(ua, 10); + uint64_t elapsed = get_time_us() - sent; + DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "RTO: DATA_RTT_us=%u jitter_us=%u KA_RTT_tb=%u expected_us=%u observed_us=%llu state=%u", + cases[i].rtt_us, cases[i].jitter_us, cases[i].ka_rtt_tb, cases[i].expected_us, (unsigned long long)elapsed, packet->state); + CHECK(packet->state == INFLIGHT_STATE_WAIT_SEND && elapsed >= cases[i].expected_us && elapsed <= cases[i].expected_us + 100000); CHECK(!sends && !a->inflight_bytes && conn->input_send_q->count == 1 && conn->unacked_bytes == 100); check_accounting(); ack(packet->seq); teardown(); }