diff --git a/src/transport_layer/etcp_tx.c b/src/transport_layer/etcp_tx.c index e4ef17f9..519dc968 100644 --- a/src/transport_layer/etcp_tx.c +++ b/src/transport_layer/etcp_tx.c @@ -16,7 +16,8 @@ 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 rtt = link->data_rtt_us; + if ((uint64_t)link->rtt_last * 100 > rtt) rtt = (uint64_t)link->rtt_last * 100; 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; @@ -101,9 +102,9 @@ static void retransmit_cb(void* arg) { while (link->attempts->head) { struct ETCP_TX_ATTEMPT* attempt = (struct ETCP_TX_ATTEMPT*)link->attempts->head; if (!attempt->sent_at_us || now - attempt->sent_at_us < rto) break; - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] RTO link=%u attempt=%u seq=%u age_us=%llu RTO_us=%u RTT_us=%u jitter_us=%u", + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] RTO link=%u attempt=%u seq=%u age_us=%llu RTO_us=%u DATA_RTT_us=%u KA_RTT_us=%u jitter_us=%u", link->etcp->log_name, link->local_link_id, (unsigned)(uint16_t)attempt->attempt_id, attempt->seq, - (unsigned long long)(now - attempt->sent_at_us), rto, link->data_rtt_us, link->data_jitter_us); + (unsigned long long)(now - attempt->sent_at_us), rto, link->data_rtt_us, link->rtt_last * 100, link->data_jitter_us); attempt_retire(link, attempt, 1, 1); } retransmit_arm(link); diff --git a/src/transport_layer/etcp_tx.h b/src/transport_layer/etcp_tx.h index ad1689d3..65f62f39 100644 --- a/src/transport_layer/etcp_tx.h +++ b/src/transport_layer/etcp_tx.h @@ -43,7 +43,9 @@ int etcp_tx_link_init(struct ETCP_LINK* link); void etcp_tx_link_reset(struct ETCP_LINK* link, int retry); void etcp_tx_link_destroy(struct ETCP_LINK* link); /* RTO = clamp(ceil(1.2*RTT + 1.5*jitter + 3000), 10000, 1000000), все величины в мкс. - * RTT/jitter — собственные DATA-измерения линка; до DATA ACK использует KA RTT, без RTT — 1с. */ + * RTT=max(DATA EWMA, последний RTT timestamp/keepalive) своего линка; jitter — только DATA, без RTT — 1с. + * Timestamp-RTT задаёт нижнюю границу, чтобы рост задержки не удалял все попытки до их ACK и не замораживал DATA-измерения. + * Это граница RTO; RTT/min_rtt и BW модели BBR по-прежнему обновляет только ACK действующей DATA-попытки. */ uint32_t etcp_tx_rto_us(const struct ETCP_LINK* link); /* Резервировать окно для packet; NULL — отказ с логом. OOM/бюджет сохраняют packet в очереди, * исчерпание номера меняет эпоху и удаляет прежний поток, включая packet. diff --git a/tests/test_etcp_windows.c b/tests/test_etcp_windows.c index 98233564..33feedfe 100644 --- a/tests/test_etcp_windows.c +++ b/tests/test_etcp_windows.c @@ -374,7 +374,9 @@ static void retransmission_timeout(void) { {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}, + {0, 0, 200, 27000}, {20000, 0, 6000, 723000}, {0, 0, UINT16_MAX, 1000000}, + {20000, 0, 1000, 123000}, {20000, 7500, 1000, 134250}, + {200000, 0, 200, 243000}, {20000, 0, 1, 27000}, {20000, 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++) { @@ -747,6 +749,24 @@ static void enable_models(void) { } } +/* Скачок RTT не уничтожает попытку до прихода ACK; уже назначенный ранний таймер проверяет новый RTO. */ +static void rto_path_rtt_growth(void) { + setup(); enable_models(); b->link_status = 0; + a->data_rtt_us = 20000; a->rtt_last = 200; + fill(a,1,1000); + struct INFLIGHT_PACKET* packet = (struct INFLIGHT_PACKET*)conn->input_wait_ack->head; + uint32_t seq = packet->seq; uint64_t id = packet->last_attempt_id; + a->rtt_last = 1000; + test_clock_run_for(ua,1000); + DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "RTO after path RTT growth: DATA_us=%u KA_us=%u RTO_us=%u attempts=%u retries=%u stalled=%u", + a->data_rtt_us,a->rtt_last*100,etcp_tx_rto_us(a),a->inflight_packets,conn->retransmissions_count,a->tx_stalled); + CHECK(queue_find_data_by_index(a->attempts,&id) && !conn->retransmissions_count && !a->tx_stalled); + etcp_tx_ack(a,seq,id,0,0,0); + CHECK(a->data_rtt_us > 20000 && bbr_calls == 1 && last_sample.rtt_us >= 100000); + CHECK(a->acked_packets == 1 && !a->inflight_bytes && !conn->unacked_bytes); + teardown(); +} + static void receive_till(struct ETCP_LINK* link, uint32_t till, int query) { struct ETCP_DGRAM* pkt = memory_pool_alloc(inst.pkt_pool); CHECK(pkt); pkt->link = link; pkt->data_len = ETCP_ACK_BASE_SIZE + query; @@ -1752,7 +1772,7 @@ int main(void) { void (*cases[])(void) = {selection, boundaries_ack_burst, late_ack_and_migration, lost_ack_and_shrink, grow_and_session, allocation_and_send_failure, removed_link_and_bbr, pacing_reset_close, initial_readiness, config_limits, timer_failure, immediate_send, ack_without_data_pacing, bbr_delivery_clock, bbr_probe_window, tcp_initial_window, - retransmission_timeout, pacing_rate_changes, pacing_timer_failure, retry_rotation, independent_attempt_acks, + rto_path_rtt_growth, retransmission_timeout, pacing_rate_changes, pacing_timer_failure, retry_rotation, independent_attempt_acks, retry_waits_for_alternative, retry_alternative_lifecycle, send_queue_scan, send_queue_input_admission, retry_full_round_history, retry_history_slot_reuse, fast_stall_progress, stalled_retry_selection, input_admission_callbacks,