diff --git a/tests/test_etcp_windows.c b/tests/test_etcp_windows.c index 8b997939..2be97259 100644 --- a/tests/test_etcp_windows.c +++ b/tests/test_etcp_windows.c @@ -773,6 +773,72 @@ static void retry_rotation(void) { ack_all(); teardown(); } +/* Готовый прежний путь не обходит занятую UP-альтернативу; ждём её события без новой попытки/RTO. */ +static void retry_waits_for_alternative(void) { + setup(); fill(a, 1, 100); + conn->initialized = 0; test_clock_run_for(ua, 120); conn->initialized = 1; + struct INFLIGHT_PACKET* packet = (struct INFLIGHT_PACKET*)conn->input_send_q->head; + CHECK(packet && packet->last_link == a && !packet->active_attempts && packet->attempt_count == 1); + b->shaper_timer = uasync_set_timeout(ua, 300, b, shaper_ready, "test_retry_alternative"); CHECK(b->shaper_timer); + DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "Retry must wait: seq=%u previous=%u ready=1 alternative=%u pacing_blocked=1", + packet->seq, a->local_link_id, b->local_link_id); + CHECK(!request()); + uint64_t allocated = ua->timer_alloc_count; + test_clock_run_for(ua, 5); + CHECK(!sends && packet->attempt_count == 1 && !packet->active_attempts && !a->retrans_timer && !b->retrans_timer); + CHECK(packet->state == INFLIGHT_STATE_WAIT_SEND && packet->last_link == a && ua->timer_alloc_count == allocated); + CHECK(etcp_tx_queue_ack(a, 99, etcp_tx_attempt_id(99, 1)) == 0); + struct ETCP_DGRAM* pkt = request(); CHECK(pkt && pkt->link == a && !pkt->payload_len && !pkt->attempt); + memory_pool_free(inst.pkt_pool, pkt); + a->burst_active = 1; a->burst_count = 1; + pkt = request(); CHECK(pkt && pkt->link == a && pkt->burst_packet && !pkt->payload_len && !pkt->attempt); + memory_pool_free(inst.pkt_pool, pkt); + CHECK(packet->last_link == a && packet->attempt_count == 1 && !packet->active_attempts); + wait_sends(1); CHECK(last_sent_link == b && packet->last_link == b && packet->attempt_count == 2); + DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "Retry resumed: seq=%u selected=%u attempts=%u", packet->seq, b->local_link_id, packet->attempt_count); + ack_all(); teardown(); + + setup(); fill(a, 1, 100); fill(b, 1, 100); b->data_rtt_us = 1000000; b->inflight_lim_bytes = 100; + conn->initialized = 0; test_clock_run_for(ua, 120); conn->initialized = 1; + packet = (struct INFLIGHT_PACKET*)conn->input_send_q->head; + CHECK(packet && packet->last_link == a && !request()); + CHECK(a->inflight_bytes == 0 && b->inflight_bytes == 100 && !packet->active_attempts && packet->attempt_count == 1); + ack(2); wait_sends(1); CHECK(last_sent_link == b && packet->last_link == b); + ack_all(); teardown(); +} + +/* Потеря последней альтернативы будит отправителя; поздний ACK убирает ожидающий повтор. */ +static void retry_alternative_lifecycle(void) { + setup(); fill(a, 1, 100); b->inflight_lim_bytes = 1; + conn->initialized = 0; test_clock_run_for(ua, 120); conn->initialized = 1; + CHECK(!request() && !conn->tx_token); + b->link_status = 0; etcp_on_link_down(conn, b); + CHECK(conn->tx_token); uasync_drain_immediate(ua); + CHECK(sends == 1 && last_sent_link == a); ack_all(); teardown(); + + setup(); fill(a, 1, 100); b->inflight_lim_bytes = 1; + conn->initialized = 0; test_clock_run_for(ua, 120); conn->initialized = 1; + CHECK(!request()); + etcp_link_close(b); b = NULL; + uasync_drain_immediate(ua); CHECK(sends == 1 && last_sent_link == a && conn->links == a && !a->next); + ack_all(); teardown(); + + setup(); fill(a, 1, 100); + conn->initialized = 0; test_clock_run_for(ua, 120); conn->initialized = 1; + struct INFLIGHT_PACKET* packet = (struct INFLIGHT_PACKET*)conn->input_send_q->head; + CHECK(packet); uint32_t seq = packet->seq; uint64_t old_id = packet->last_attempt_id; + b->shaper_timer = uasync_set_timeout(ua, 300, b, shaper_ready, "test_retry_late_ack"); CHECK(b->shaper_timer); + CHECK(!request()); etcp_tx_ack(a, seq, old_id, 0, 0, 0); + test_clock_run_for(ua, 40); CHECK(!sends && !conn->unacked_bytes && !conn->active_attempts && !conn->input_send_q->count); + teardown(); + + setup(); fill(a, 1, 100); + conn->initialized = 0; test_clock_run_for(ua, 120); conn->initialized = 1; + b->initialized = 0; + struct ETCP_DGRAM* pkt = request(); CHECK(pkt && pkt->link == a); + memory_pool_free(inst.pkt_pool, pkt); ack_all(); teardown(); +} + /* Поздняя копия и общий till не освобождают окно/не обучают модель другого канала. */ static void independent_attempt_acks(void) { setup(); enable_models(); fill(a, 1, 100); @@ -1227,6 +1293,7 @@ int main(void) { 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, + retry_waits_for_alternative, retry_alternative_lifecycle, receive_admission_and_path_acks, peer_window_queries, pacing_continuity, pacing_data_units, attempt_memory_limits, normalizer_rx_oom, normalizer_tx_oom, normalizer_callback_abort, reset_without_allocation, restart_allocation_failures, receiver_bandwidth_bounds, receiver_point_lifecycle, receiver_point_wire, receiver_point_truncated,