diff --git a/tests/test_etcp_windows.c b/tests/test_etcp_windows.c index 2be97259..485f74f1 100644 --- a/tests/test_etcp_windows.c +++ b/tests/test_etcp_windows.c @@ -839,6 +839,81 @@ static void retry_alternative_lifecycle(void) { memory_pool_free(inst.pkt_pool, pkt); ack_all(); teardown(); } +/* Заблокированный повтор не держит следующие DATA; среди готовых пакетов повтор имеет приоритет. */ +static void send_queue_scan(void) { + setup(); fill(a, 1, 100); + conn->initialized = 0; test_clock_run_for(ua, 120); conn->initialized = 1; + struct INFLIGHT_PACKET* blocked = (struct INFLIGHT_PACKET*)conn->input_send_q->head; CHECK(blocked); + b->inflight_lim_bytes = 1; + struct INFLIGHT_PACKET* fresh = enqueue(80); + struct ETCP_DGRAM* pkt = request(); + DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "Send scan: blocked=%u fresh=%u selected=%u", blocked->seq, fresh->seq, + pkt && pkt->attempt ? pkt->attempt->seq : fresh->last_link ? fresh->seq : 0); + CHECK(pkt && pkt->link == a && fresh->state == INFLIGHT_STATE_WAIT_ACK && blocked->state == INFLIGHT_STATE_WAIT_SEND); + CHECK(blocked->attempt_count == 1 && !blocked->active_attempts); + memory_pool_free(inst.pkt_pool, pkt); ack_all(); teardown(); + + setup(); fill(a, 1, 100); fill(b, 1, 100); + a->data_rtt_us = 10000; b->data_rtt_us = 20000; + conn->initialized = 0; test_clock_run_for(ua, 120); conn->initialized = 1; + blocked = (struct INFLIGHT_PACKET*)queue_find_data_by_index(conn->input_send_q, &(uint32_t){1}); + struct INFLIGHT_PACKET* later = (struct INFLIGHT_PACKET*)queue_find_data_by_index(conn->input_send_q, &(uint32_t){2}); + CHECK(blocked && later && conn->input_send_q->head == &blocked->ll); + b->inflight_lim_bytes = 1; + pkt = request(); CHECK(pkt && pkt->link == a && later->last_link == a && blocked->state == INFLIGHT_STATE_WAIT_SEND); + memory_pool_free(inst.pkt_pool, pkt); ack_all(); teardown(); + + setup(); fill(a, 1, 100); fresh = enqueue(80); + conn->initialized = 0; test_clock_run_for(ua, 120); conn->initialized = 1; + CHECK(conn->input_send_q->head == &fresh->ll); + pkt = request(); CHECK(pkt && pkt->link == b && fresh->state == INFLIGHT_STATE_WAIT_SEND); + memory_pool_free(inst.pkt_pool, pkt); + pkt = request(); CHECK(pkt && fresh->state == INFLIGHT_STATE_WAIT_ACK); + memory_pool_free(inst.pkt_pool, pkt); ack_all(); teardown(); + + setup(); fill(a, 256, 100); + conn->initialized = 0; test_clock_run_for(ua, 120); conn->initialized = 1; + b->inflight_lim_bytes = 1; fresh = enqueue(80); + uint64_t started = get_time_us(); pkt = request(); + DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "Send scan: blocked_prefix=256 elapsed_us=%llu", (unsigned long long)(get_time_us() - started)); + CHECK(pkt && pkt->link == a && fresh->state == INFLIGHT_STATE_WAIT_ACK && conn->input_send_q->count == 256); + memory_pool_free(inst.pkt_pool, pkt); ack_all(); teardown(); +} + +static void queued_input_fragment(unsigned len) { + struct ETCP_FRAGMENT* fragment = (struct ETCP_FRAGMENT*)queue_entry_new_from_pool(conn->io_pool); CHECK(fragment); + fragment->ll.dgram = memory_pool_alloc(inst.data_pool); CHECK(fragment->ll.dgram); + fragment->ll.dgram_pool = inst.data_pool; fragment->ll.len = len; memset(fragment->ll.dgram, 0x5a, len); + conn->input_queue->callback_suspended = 1; + CHECK(queue_data_put(conn->input_queue, &fragment->ll) == 0); +} + +/* Полностью заблокированная send_q не мешает подгрузке, но общий лимит и смена эпохи сохраняются. */ +static void send_queue_input_admission(void) { + setup(); fill(a, 1, 100); + conn->initialized = 0; test_clock_run_for(ua, 120); conn->initialized = 1; + b->inflight_lim_bytes = 1; queued_input_fragment(80); + CHECK(conn->input_queue->count == 1 && conn->input_send_q->count == 1); + struct ETCP_DGRAM* pkt = request(); CHECK(pkt && pkt->link == a && pkt->payload_len == 80); + CHECK(!conn->input_queue->count && conn->input_send_q->count == 1 && conn->input_wait_ack->count == 1); + memory_pool_free(inst.pkt_pool, pkt); ack_all(); teardown(); + + setup(); fill(a, 1, 100); + conn->initialized = 0; test_clock_run_for(ua, 120); conn->initialized = 1; + b->inflight_lim_bytes = 1; conn->optimal_inflight = 100; queued_input_fragment(80); + CHECK(!request() && conn->input_queue->count == 1 && conn->next_tx_id == 2 && conn->unacked_bytes == 100); + ack(1); pkt = request(); CHECK(pkt && pkt->payload_len == 80 && !conn->input_queue->count); + memory_pool_free(inst.pkt_pool, pkt); ack_all(); teardown(); + + setup(); fill(a, 1, 100); + conn->initialized = 0; test_clock_run_for(ua, 120); conn->initialized = 1; + b->inflight_lim_bytes = 1; queued_input_fragment(80); fail_tx_inflight = 1; + uint32_t reset = conn->reset_count; + CHECK(!request() && !fail_tx_inflight && conn->reset_count != reset && conn->reinit_pending); + CHECK(!conn->unacked_bytes && !conn->active_attempts && !conn->input_send_q->count && !conn->input_queue->count); + teardown(); +} + /* Поздняя копия и общий till не освобождают окно/не обучают модель другого канала. */ static void independent_attempt_acks(void) { setup(); enable_models(); fill(a, 1, 100); @@ -1294,6 +1369,7 @@ int main(void) { 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, + send_queue_scan, send_queue_input_admission, 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,