diff --git a/src/routing_layer/topo_group_connect.h b/src/routing_layer/topo_group_connect.h index 40060261..f533375f 100644 --- a/src/routing_layer/topo_group_connect.h +++ b/src/routing_layer/topo_group_connect.h @@ -12,8 +12,8 @@ * Успех — транспортный UP; группа удерживает сессию до DOWN, обмен таблицами * продолжается отдельно. На класс не более двух попыток, CONNECTING: 2 s. * Автоповторы с backoff 1,2,4,8,16,30 s; проверка списка раз в 1 s, пока цель - * суперузлов не достигнута. Без UP всех суперузлов не переходим к desktop, - * пока каждый суперузел не проверен; mobile разрешён после проверки desktop + * суперузлов не достигнута. При отсутствии UP-суперузлов desktop разрешён + * только после проверки каждого суперузла; mobile разрешён после проверки desktop * и лишь при отсутствии UP обоих старших классов. Уже поднятые резервы сохраняются. * В standby новые автоматические попытки ждут burst; ручная стартует сразу. * READY/recovery не изменяются. diff --git a/tests/test_invite_group_create.c b/tests/test_invite_group_create.c index 6ea0a904..e7c7832b 100644 --- a/tests/test_invite_group_create.c +++ b/tests/test_invite_group_create.c @@ -238,6 +238,20 @@ int main(void) { REQUIRE(reply.calls == 1 && reply.event == TGI_EVENT_TIMEOUT && !a->group_invites); fail_request_send = 0; + /* Реальный общий таймер завершает молчащий запрос, сохраняя чужое владение NCD. */ + REQUIRE(begin_wait(a, gid, &node, &reply) == 0); + deadline = get_time_tb() + 30000; + while (!reply.calls && get_time_tb() < deadline) uasync_poll(ua, 20); + REQUIRE(reply.calls == 1 && reply.event == TGI_EVENT_TIMEOUT && !a->group_invites); + REQUIRE(node_conn_direct_get_conn(owner_handle) == conn && !conn->close_requested); + + /* DOWN завершает запрос немедленно; повторное UP не вызывает второй callback. */ + REQUIRE(begin_wait(a, gid, &node, &reply) == 0); + conn->links_up = 0; etcp_cbk_fire(conn, ETCP_CBK_EVENT_DOWN); + REQUIRE(reply.calls == 1 && reply.event == TGI_EVENT_TIMEOUT && !a->group_invites); + conn->links_up = 1; etcp_cbk_fire(conn, ETCP_CBK_EVENT_UP); + REQUIRE(reply.calls == 1 && node_conn_direct_get_conn(owner_handle) == conn && !conn->close_requested); + /* Ошибка записи проверенного описания обязана запрещать успешный callback. */ REQUIRE(sqlite3_exec(a->topo_sqlite_db, "CREATE TEMP TRIGGER invite_write_fail BEFORE INSERT ON channels " "BEGIN SELECT RAISE(ABORT,'invite audit failure'); END", NULL, NULL, NULL) == SQLITE_OK); @@ -251,7 +265,7 @@ int main(void) { REQUIRE(!memcmp(x25519, expected_x, 32) && !memcmp(ed25519, expected_ed, 32)); REQUIRE(!topo_node_find_by_id(group_a, b->node_id)); REQUIRE(node_conn_direct_get_conn(owner_handle) == conn && !conn->close_requested); - DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "test invite fault checks: %zu truncations, isolation, framing, signature, OOM, timer, send, SQL PASS", + DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "test invite: %zu truncations, isolation, framing, signature, OOM, timer, DOWN, send, SQL PASS", valid_size); result = 0; cleanup: