Cover ACK batching deadlines, timer races, and failure cleanup
This commit is contained in:
1 parent
6ab2526966
commit
2edf09fde1
1 file changed
+119
-1
+119
-1
@@ -217,6 +217,12 @@ static struct ETCP_DGRAM* request(void) {
|
||||
return pkt;
|
||||
}
|
||||
|
||||
/* Wire/window-тесты моделируют уже ожидающий ACK, не меняя часы и не запуская sender через uasync. */
|
||||
static void mature_acks(struct ETCP_LINK* link) {
|
||||
for (struct ll_entry* e = link->ack_q->head; e; e = e->next)
|
||||
((struct ACK_PACKET*)e)->received_at_us = get_time_us() - ETCP_ACK_DELAY_US;
|
||||
}
|
||||
|
||||
static void fill(struct ETCP_LINK* target, unsigned count, unsigned len) {
|
||||
int up_a = a->link_status, up_b = b->link_status;
|
||||
a->link_status = target == a; b->link_status = target == b;
|
||||
@@ -801,6 +807,7 @@ static void retry_waits_for_alternative(void) {
|
||||
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);
|
||||
mature_acks(a);
|
||||
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;
|
||||
@@ -1152,6 +1159,111 @@ static void ack_absolute_deadline(void) {
|
||||
teardown();
|
||||
}
|
||||
|
||||
static void ack_piggyback_deadlines(void) {
|
||||
setup(); conn->got_initial_pkt = 1;
|
||||
ack_now_us = get_time_us(); ack_clock_active = capture_ack_timer = 1;
|
||||
uint64_t begin = ack_now_us;
|
||||
receive_fragment(a, 2, 1); ack_now_us += 1000; receive_fragment(b, 3, 1);
|
||||
CHECK(conn->ack_resp_deadline_us == begin + ETCP_ACK_DELAY_US);
|
||||
enqueue(100);
|
||||
struct ETCP_DGRAM* pkt = request(); CHECK(pkt && pkt->link == a && pkt->payload_len && pkt->data[1] == 1);
|
||||
memory_pool_free(inst.pkt_pool, pkt); CHECK(!a->ack_q->count && b->ack_q->count == 1);
|
||||
CHECK(conn->ack_resp_timer && conn->ack_resp_deadline_us == begin + 3000);
|
||||
ack_now_us = begin + 2000; CHECK(!request());
|
||||
ack_now_us = begin + 2999; CHECK(!request());
|
||||
ack_now_us++; pkt = request(); CHECK(pkt && pkt->link == b && !pkt->payload_len && pkt->data[1] == 1);
|
||||
memory_pool_free(inst.pkt_pool, pkt); CHECK(!conn->ack_resp_timer && !conn->ack_resp_deadline_us);
|
||||
ack_all(); teardown();
|
||||
}
|
||||
|
||||
/* Частичный piggyback меняет голову: прежний deadline больше не делает молодой ACK готовым. */
|
||||
static void ack_partial_piggyback(void) {
|
||||
setup(); conn->got_initial_pkt = 1; b->link_status = 0;
|
||||
ack_now_us = get_time_us(); ack_clock_active = capture_ack_timer = 1;
|
||||
uint64_t begin = ack_now_us;
|
||||
receive_fragment(a, 2, 1); ack_now_us += 500; receive_fragment(a, 3, 1);
|
||||
unsigned max_body = a->mtu - WIRE_OVERHEAD(AF_INET);
|
||||
enqueue(max_body - ETCP_ACK_BASE_SIZE - ETCP_PAYLOAD_HEADER_SIZE - ETCP_ACK_ENTRY_SIZE - ETCP_ACK_RX_POINT_SIZE);
|
||||
ack_now_us = begin + 1999;
|
||||
struct ETCP_DGRAM* pkt = request(); CHECK(pkt && pkt->payload_len && pkt->data[1] == 1 && a->ack_q->count == 1);
|
||||
memory_pool_free(inst.pkt_pool, pkt); CHECK(conn->ack_resp_deadline_us == begin + 2500);
|
||||
timeout_callback_t callback = captured_ack_callback; void* arg = captured_ack_arg;
|
||||
ack_now_us = begin + 2000; uasync_cancel_timeout(ua, conn->ack_resp_timer); callback(arg);
|
||||
CHECK(conn->ack_resp_timer && conn->ack_resp_deadline_us == begin + 2500 && !request());
|
||||
ack_now_us = begin + 2100; receive_fragment(a, 4, 1);
|
||||
CHECK(conn->ack_resp_deadline_us == begin + 2500);
|
||||
ack_now_us = begin + 2500; pkt = request(); CHECK(pkt && !pkt->payload_len && pkt->data[1] == 2);
|
||||
memory_pool_free(inst.pkt_pool, pkt); CHECK(!a->ack_q->count && !conn->ack_resp_timer);
|
||||
ack_all(); teardown();
|
||||
}
|
||||
|
||||
/* Новый RX между timer и отложенным sender не запускает для старой головы очередные 2мс. */
|
||||
static void ack_timer_interleaving(void) {
|
||||
setup(); conn->got_initial_pkt = 1;
|
||||
ack_now_us = get_time_us(); ack_clock_active = capture_ack_timer = 1;
|
||||
uint64_t begin = ack_now_us;
|
||||
receive_fragment(a, 2, 1);
|
||||
timeout_callback_t callback = captured_ack_callback; void* arg = captured_ack_arg;
|
||||
ack_now_us += ETCP_ACK_DELAY_US;
|
||||
uasync_cancel_timeout(ua, conn->ack_resp_timer); callback(arg);
|
||||
CHECK(!conn->ack_resp_timer && conn->tx_token);
|
||||
receive_fragment(a, 3, 1); CHECK(!conn->ack_resp_timer && conn->tx_token);
|
||||
struct ETCP_DGRAM* pkt = request(); CHECK(pkt && pkt->data[1] == 2 && !pkt->payload_len);
|
||||
const uint8_t* entry = pkt->data + ETCP_ACK_BASE_SIZE;
|
||||
uint32_t delay = (uint32_t)entry[6] | ((uint32_t)entry[7] << 8) | ((uint32_t)entry[8] << 16) | ((uint32_t)entry[9] << 24);
|
||||
CHECK(delay == ETCP_ACK_DELAY_US && ack_now_us == begin + ETCP_ACK_DELAY_US);
|
||||
memory_pool_free(inst.pkt_pool, pkt); teardown();
|
||||
}
|
||||
|
||||
static void ack_urgent_and_oom(void) {
|
||||
for (unsigned mode = 0; mode < 2; mode++) {
|
||||
setup(); conn->got_initial_pkt = 1;
|
||||
ack_now_us = get_time_us(); ack_clock_active = 1;
|
||||
fail_ack_timer = mode;
|
||||
receive_fragment(a, 2, 1);
|
||||
if (!mode) receive_till(a, 0, 1);
|
||||
CHECK(a->ack_reply_pending && !fail_ack_timer);
|
||||
CHECK(((struct ACK_PACKET*)a->ack_q->head)->received_at_us == ack_now_us);
|
||||
struct ETCP_DGRAM* pkt = request(); CHECK(pkt && pkt->data[1] == 1 && !pkt->payload_len);
|
||||
memory_pool_free(inst.pkt_pool, pkt);
|
||||
CHECK(!a->ack_reply_pending && !conn->ack_resp_timer && !conn->ack_resp_deadline_us);
|
||||
teardown();
|
||||
}
|
||||
}
|
||||
|
||||
static void ack_partial_frame_and_lifecycle(void) {
|
||||
setup(); ack_now_us = get_time_us(); ack_clock_active = 1;
|
||||
for (unsigned n = 0; n < 200; n++) CHECK(etcp_tx_queue_ack(a, 100+n, etcp_tx_attempt_id(100+n, 1)) == 0);
|
||||
ack_now_us += ETCP_ACK_DELAY_US;
|
||||
struct ETCP_DGRAM* pkt = request(); CHECK(pkt && pkt->data[1] && a->ack_q->count);
|
||||
memory_pool_free(inst.pkt_pool, pkt); CHECK(!conn->ack_resp_timer);
|
||||
pkt = request(); CHECK(pkt && pkt->data[1] && !a->ack_q->count);
|
||||
memory_pool_free(inst.pkt_pool, pkt); teardown();
|
||||
|
||||
setup(); ack_now_us = get_time_us(); ack_clock_active = 1;
|
||||
conn->ack_till_deadline_us = ack_now_us + ETCP_ACK_DELAY_US; conn->last_delivered_id = 17;
|
||||
CHECK(!request() && conn->ack_resp_timer);
|
||||
ack_now_us += ETCP_ACK_DELAY_US;
|
||||
pkt = request(); CHECK(pkt && !pkt->data[1] && pkt->data[2] == 17);
|
||||
memory_pool_free(inst.pkt_pool, pkt); CHECK(!conn->ack_till_deadline_us && !conn->ack_resp_timer);
|
||||
CHECK(etcp_tx_queue_ack(a, 99, etcp_tx_attempt_id(99, 1)) == 0); CHECK(!request() && conn->ack_resp_timer);
|
||||
etcp_conn_reset(conn); CHECK(!conn->ack_resp_timer && !conn->ack_resp_deadline_us && !conn->ack_till_deadline_us);
|
||||
teardown();
|
||||
}
|
||||
|
||||
static void ack_sender_failure(void) {
|
||||
setup(); conn->got_initial_pkt = 1; CHECK(etcp_conn_ref_take(conn) == 0);
|
||||
ack_now_us = get_time_us(); ack_clock_active = capture_ack_timer = 1;
|
||||
receive_fragment(a, 2, 1);
|
||||
timeout_callback_t callback = captured_ack_callback; void* arg = captured_ack_arg;
|
||||
ack_now_us += ETCP_ACK_DELAY_US; fail_tx_timer = 1;
|
||||
uasync_cancel_timeout(ua, conn->ack_resp_timer); callback(arg);
|
||||
CHECK(!fail_tx_timer && conn->close_requested && !conn->ack_resp_timer);
|
||||
ack_clock_active = 0; uasync_drain_immediate(ua);
|
||||
CHECK(conn->state == 2 && !conn->links && !conn->ack_resp_deadline_us && !conn->ack_till_deadline_us);
|
||||
etcp_conn_ref_free(conn); teardown();
|
||||
}
|
||||
|
||||
static void receive_admission_and_path_acks(void) {
|
||||
setup(); conn->got_initial_pkt = 1;
|
||||
fail_rx_data = 1; receive_fragment(a, 2, 1);
|
||||
@@ -1278,6 +1390,7 @@ static void receiver_point_wire(void) {
|
||||
CHECK(a->rx_data_bytes == 2 && a->ack_q->count == 1 && conn->recv_q->count == 1);
|
||||
a->rx_data_bytes = UINT64_C(0xfedcba9876543210); a->rx_data_time_us = UINT64_C(0x0011223344556677);
|
||||
const uint8_t expected[] = {0x10,0x32,0x54,0x76,0x98,0xba,0xdc,0xfe,0x77,0x66,0x55,0x44,0x33,0x22,0x11,0x00};
|
||||
mature_acks(a);
|
||||
struct ETCP_DGRAM* pkt = request(); CHECK(pkt && pkt->data[1] == 1 && !pkt->payload_len);
|
||||
CHECK(pkt->data_len == etcp_ack_section_size(1) && !memcmp(pkt->data+ETCP_ACK_BASE_SIZE+ETCP_ACK_ENTRY_SIZE,expected,16));
|
||||
memory_pool_free(inst.pkt_pool,pkt);
|
||||
@@ -1294,12 +1407,14 @@ static void receiver_point_wire(void) {
|
||||
else CHECK(!pkt->data[1] && a->ack_q->count == 1);
|
||||
memory_pool_free(inst.pkt_pool,pkt);
|
||||
if (a->ack_q->count) {
|
||||
mature_acks(a);
|
||||
pkt = request(); CHECK(pkt && pkt->data[1] == 1 && !pkt->payload_len);
|
||||
memory_pool_free(inst.pkt_pool,pkt);
|
||||
}
|
||||
ack(seq);
|
||||
}
|
||||
for (unsigned i = 100; i < 300; i++) receive_fragment(a,i,1000+i);
|
||||
mature_acks(a);
|
||||
unsigned count = (max_body-ETCP_ACK_BASE_SIZE-ETCP_ACK_RX_POINT_SIZE)/ETCP_ACK_ENTRY_SIZE;
|
||||
pkt = request(); CHECK(pkt && pkt->data[1] == count && pkt->data_len <= max_body && a->ack_q->count == 200-(int)count);
|
||||
memory_pool_free(inst.pkt_pool,pkt);
|
||||
@@ -1500,6 +1615,7 @@ static void compact_attempt_identity(void) {
|
||||
conn->got_initial_pkt = 1;
|
||||
receive_fragment(a,2,1); receive_fragment(a,3,1); receive_fragment(a,2,1);
|
||||
CHECK(a->ack_q->count == 2 && a->rx_data_bytes == 3);
|
||||
mature_acks(a);
|
||||
struct ETCP_DGRAM* pkt = request(); CHECK(pkt && !pkt->payload_len && pkt->data[1] == 2);
|
||||
const uint8_t entry1[] = {2,0,0,0,1,0}, entry2[] = {3,0,0,0,1,0};
|
||||
CHECK(!memcmp(pkt->data+ETCP_ACK_BASE_SIZE,entry1,sizeof(entry1)));
|
||||
@@ -1573,7 +1689,9 @@ int main(void) {
|
||||
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,
|
||||
ack_absolute_deadline, receive_admission_and_path_acks, peer_window_queries, pacing_continuity, pacing_data_units, attempt_memory_limits,
|
||||
ack_absolute_deadline, ack_piggyback_deadlines, ack_partial_piggyback, ack_timer_interleaving,
|
||||
ack_urgent_and_oom, ack_partial_frame_and_lifecycle, ack_sender_failure,
|
||||
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,
|
||||
compact_attempt_identity, compact_attempt_exhaustion, compact_sequence_exhaustion,
|
||||
|
||||
Reference in new issue
Block a user