Browse Source

Fix peer standby cancellation on normal keepalive from a new link

master
evgeny 3 days ago
parent
commit
919c3d76fe
  1. 36
      src/transport_layer/etcp_keepalive.c
  2. 58
      tests/test_etcp_server_lifecycle.c
  3. 37
      tests/test_standby_transport.c

36
src/transport_layer/etcp_keepalive.c

@ -561,21 +561,29 @@ int etcp_keepalive_on_recv(struct ETCP_DGRAM* pkt) {
(int)(sleep_duration / 10), (int)(sleep_duration % 10));
if (!link->is_tcp)
ka_send_sleep_ack(link); /* TCP доставляет надёжно — ACK не нужен */
} else if (link->ka_peer_sleeping) {
/* Обычный keepalive (без флагов): пир проснулся — вернуться в normal-режим. */
link->ka_peer_sleeping = 0;
link->ka_period_ms = (uint16_t)link->keepalive_interval;
link->keepalive_timeout = (uint32_t)link->keepalive_interval * KA_TIMEOUT_MULT;
if (link->etcp) {
if (link->etcp->peer_phase_timer) {
uasync_cancel_timeout(link->etcp->instance->ua, link->etcp->peer_phase_timer);
link->etcp->peer_phase_timer = NULL;
}
ka_peer_set_phase(link->etcp, 0);
} else {
/* Обычный keepalive снимает сон соединения даже по новому линку без ka_peer_sleeping. */
struct ETCP_CONN* conn = link->etcp;
int link_was_sleeping = link->ka_peer_sleeping;
if (link_was_sleeping || conn->peer_sleep_phase || conn->peer_phase_timer) {
DEBUG_INFO(DEBUG_CATEGORY_KEEPALIVE,
"[%s] peer normal link=%u tcp=%u link_sleep=%u phase=%u phase_timer=%u: cancel standby",
conn->log_name, link->local_link_id, link->is_tcp, link->ka_peer_sleeping,
conn->peer_sleep_phase, conn->peer_phase_timer != NULL);
}
if (link_was_sleeping) {
link->ka_peer_sleeping = 0;
link->ka_period_ms = (uint16_t)link->keepalive_interval;
link->keepalive_timeout = (uint32_t)link->keepalive_interval * KA_TIMEOUT_MULT;
}
if (conn->peer_phase_timer) {
uasync_cancel_timeout(instance->ua, conn->peer_phase_timer);
conn->peer_phase_timer = NULL;
}
DEBUG_INFO(DEBUG_CATEGORY_KEEPALIVE, "[%s] peer woke up, back to normal (interval=%dms)",
link->etcp->log_name, link->keepalive_interval);
if (!link->ka_my_sleeping)
conn->peer_active_remaining = conn->peer_sleep_duration = 0;
ka_peer_set_phase(conn, 0);
/* Повторный normal RX не должен отодвигать очередную отправку keepalive. */
if (link_was_sleeping && !link->ka_my_sleeping)
restart_keepalive_timer_ms(link, link->ka_period_ms);
}

58
tests/test_etcp_server_lifecycle.c

@ -230,6 +230,60 @@ static void check_wake_deadline(void) {
teardown(); DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "PASS wake replaces long sleep deadline with normal timeout");
}
static void peer_phase_changed(struct UTUN_INSTANCE* owner, uint64_t peer, int sleeping, void* arg) {
unsigned* events = arg;
CHECK(owner == &inst && peer == 42);
events[sleeping ? 1 : 0]++;
}
static void receive_keepalive(struct ETCP_LINK* link, uint8_t flags, uint16_t active) {
struct ETCP_DGRAM* pkt = memory_pool_alloc(inst.pkt_pool); CHECK(pkt);
memset(pkt, 0, sizeof(*pkt) + 8); pkt->link = link;
pkt->data_len = flags & KA_FLAG_SLEEP ? 8 : 4;
pkt->data[0] = ETCP_KEEPALIVE; pkt->data[1] = flags;
pkt->data[2] = 200; pkt->data[4] = active & 0xff; pkt->data[5] = active >> 8;
pkt->data[6] = 200; // 20 секунд сна
link->last_recv_local_time = get_time_tb();
CHECK(etcp_keepalive_on_recv(pkt) == 1);
}
/* Новый линк снимает сон всего соединения, включая ещё не наступившую SLEEP-фазу. */
static void check_peer_wake_new_link(int tcp, int active_window) {
setup(); struct ETCP_CONN* conn = ready_conn(!tcp); struct ETCP_LINK* old = conn->links;
unsigned events[2] = {0}; utun_add_peer_sleep_cbk(&inst, peer_phase_changed, events);
receive_keepalive(old, KA_FLAG_SLEEP, active_window ? 5 : 0);
CHECK(conn->peer_phase_timer && conn->peer_sleep_phase == !active_window);
CHECK(events[1] == (unsigned)!active_window && !events[0]);
/* REINIT потока сам по себе не означает пробуждение приложения. */
etcp_conn_reinit_id(conn, "peer restart before wake", conn->reset_id);
CHECK(conn->peer_phase_timer && conn->peer_sleep_phase == !active_window);
struct ETCP_LINK* fresh = add_link(conn, tcp); start_keepalive_timer(fresh);
CHECK(!fresh->ka_peer_sleeping && fresh->keepalive_timer);
receive_keepalive(fresh, KA_FLAG_SLEEP_ACK, 0);
CHECK(conn->peer_phase_timer && conn->peer_sleep_phase == !active_window);
void* timer = fresh->keepalive_timer;
receive_keepalive(fresh, 0, 0);
CHECK(!conn->peer_sleep_phase && !conn->peer_phase_timer && fresh->keepalive_timer == timer);
CHECK(events[0] == (unsigned)!active_window && old->ka_peer_sleeping);
uint64_t allocations = inst.ua->timer_alloc_count;
receive_keepalive(fresh, 0, 0);
CHECK(inst.ua->timer_alloc_count == allocations && events[0] == (unsigned)!active_window);
/* Частый RX не отодвигает TX; отменённый фазовый таймер не усыпляет снова. */
unsigned packets = fresh->keepalive_sent_count;
for (unsigned i = 0; i < 6; i++) {
test_clock_run_for(inst.ua, 1000); receive_keepalive(fresh, 0, 0);
CHECK(!conn->peer_sleep_phase && !conn->peer_phase_timer);
}
CHECK(fresh->keepalive_sent_count > packets && events[0] == (unsigned)!active_window);
/* Пробуждение пира не выключает собственный standby на прежнем линке. */
old->ka_my_sleeping = 1; receive_keepalive(old, 0, 0);
CHECK(old->ka_my_sleeping && !old->ka_peer_sleeping && !old->keepalive_timer);
CHECK(!conn->peer_phase_timer && events[0] == (unsigned)!active_window);
utun_remove_peer_sleep_cbk(&inst, peer_phase_changed, events);
teardown();
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "PASS peer wake via new link tcp=%d active_window=%d: phase cancelled, TX preserved", tcp, active_window);
}
/* Мягкое закрытие работает без NCD entry; KEEP_ALIVE сохраняет транспорт дольше прежних 5 секунд. */
static void check_graceful(void) {
setup(); struct ETCP_CONN* conn = ready_conn(0);
@ -251,6 +305,7 @@ int main(void) {
debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN); test_clock_init(10000);
debug_set_category_level(DEBUG_CATEGORY_CONNECTION, DEBUG_LEVEL_DEBUG);
debug_set_category_level(DEBUG_CATEGORY_KEEPALIVE, DEBUG_LEVEL_DEBUG);
debug_set_category_level(DEBUG_CATEGORY_DEBUG, DEBUG_LEVEL_INFO);
size_t baseline = u_get_allocated_count();
check_pending(); CHECK(u_get_allocated_count() == baseline);
check_reconnect(); CHECK(u_get_allocated_count() == baseline);
@ -262,6 +317,9 @@ int main(void) {
check_sockless_tcp_keepalive(); CHECK(u_get_allocated_count() == baseline);
check_sleep(); CHECK(u_get_allocated_count() == baseline);
check_wake_deadline(); CHECK(u_get_allocated_count() == baseline);
for (int tcp = 0; tcp <= 1; tcp++) for (int active = 0; active <= 1; active++) {
check_peer_wake_new_link(tcp, active); CHECK(u_get_allocated_count() == baseline);
}
check_remote_down(); CHECK(u_get_allocated_count() == baseline);
check_graceful(); CHECK(u_get_allocated_count() == baseline);
test_clock_finish(); return 0;

37
tests/test_standby_transport.c

@ -162,6 +162,41 @@ static void check_transitions(void) {
DEBUG_INFO(DEBUG_CATEGORY_KEEPALIVE, "[PASS] first background, repeated commands, UDP/TCP ready, phase-before-work, peer sleep and cleanup");
}
/* GUI прерывает даже min_sleep; быстрый возврат в foreground отменяет будущие фазы. */
static void check_foreground_wake(void) {
setup(); standby_set_intervals_ms(2000, 120000, 90000);
assert(utun_set_client_activity(&inst, 0) == 0);
test_clock_run_for(ua, 21000);
assert(standby_get_sleep_tb() > 1190000);
standby_notify_network_activity();
assert(standby_get_sleep_tb() > 1190000); // сеть не отменяет min_sleep
wait_after_packets = sent[0].count; assert(standby_wait(NULL, waited));
uint64_t before = get_time_tb();
assert(utun_set_client_activity(&inst, 1) == 0);
assert(get_time_tb() - before < 1000 && wait_calls == 1 && !standby_is_enabled());
for (int i = 0; i < 3; i++) {
assert(!links[i].ka_my_sleeping && !links[i].ka_sleep_announce_timer && links[i].keepalive_timer);
assert(sent[i].flags == 0);
}
/* При потере первого normal-пакета следующий уходит по обычному таймеру UDP/TCP. */
unsigned udp_packets = sent[0].count, tcp_packets = sent[2].count;
test_clock_run_for(ua, 21000);
assert(sent[0].count > udp_packets && sent[2].count > tcp_packets);
assert(sent[0].flags == 0 && sent[2].flags == 0 && !standby_is_enabled());
for (unsigned i = 0; i < 3; i++) {
assert(utun_set_client_activity(&inst, 0) == 0 && standby_is_enabled());
test_clock_run_for(ua, 100);
assert(utun_set_client_activity(&inst, 1) == 0 && !standby_is_enabled());
}
test_clock_run_for(ua, 25000);
assert(!standby_is_enabled() && inst.client_activity == CLIENT_ACTIVITY_ACTIVE);
for (int i = 0; i < 3; i++) assert(!links[i].ka_my_sleeping && !links[i].ka_sleep_announce_timer);
assert(utun_set_client_activity(&inst, 0) == 0);
test_clock_run_for(ua, 21000); assert(standby_get_sleep_tb() > 1190000);
teardown();
DEBUG_INFO(DEBUG_CATEGORY_KEEPALIVE, "[PASS] foreground bypasses min_sleep, repeated normal TX, rapid hide/show and final background sleep");
}
/* Ошибка ENTER сохраняет normal; потеря фазового таймера возвращает normal, без зависания сна. */
static void check_timer_failures(void) {
setup(); assert(utun_set_client_activity(&inst, 1) == 0);
@ -182,7 +217,7 @@ static void check_timer_failures(void) {
int main(void) {
debug_config_init(); debug_set_level(DEBUG_LEVEL_INFO);
test_clock_init(1000);
check_transitions(); check_transitions(); check_timer_failures();
check_transitions(); check_transitions(); check_foreground_wake(); check_timer_failures();
DEBUG_INFO(DEBUG_CATEGORY_KEEPALIVE, "PASS: standby transport regression tests");
test_clock_finish();
return 0;

Loading…
Cancel
Save