From 21d8b3844e2b2b5e11dd949b559114f73addda59 Mon Sep 17 00:00:00 2001 From: evgeny Date: Sat, 24 Oct 2026 15:36:12 +0200 Subject: [PATCH] Apply refreshed peer priorities without replacing group requests --- src/routing_layer/topo_group_connect.c | 10 ++++++-- src/routing_layer/topo_group_connect.h | 33 ++++++++++++++++++++------ tests/test_group_connect.c | 13 ++++++++++ 3 files changed, 47 insertions(+), 9 deletions(-) diff --git a/src/routing_layer/topo_group_connect.c b/src/routing_layer/topo_group_connect.c index 8e3dbe45..67f01991 100644 --- a/src/routing_layer/topo_group_connect.c +++ b/src/routing_layer/topo_group_connect.c @@ -28,6 +28,7 @@ static const char* const tgc_names[] = { "unknown", "supernode", "desktop", "mob struct tgc_candidate { uint64_t node_id, started, retry_at, retry_delay; + size_t priority; // текущая позиция в SQL-порядке connected/RTT/node_id struct TOPO_PEER_REQUEST* request; uint8_t kind, tested, manual, listed; }; @@ -85,7 +86,7 @@ static struct tgc_candidate* tgc_add(struct TOPO_GROUP_CONNECT* gc, uint64_t id) struct tgc_candidate* list = u_realloc(gc->candidates, (gc->count + 1) * sizeof(*list)); if (!list) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "group candidate allocation failed"); return NULL; } gc->candidates = list; - list[gc->count] = (struct tgc_candidate){ .node_id = id, .retry_delay = TGC_PAUSE_TB }; + list[gc->count] = (struct tgc_candidate){ .node_id = id, .retry_delay = TGC_PAUSE_TB, .priority = SIZE_MAX }; return &list[gc->count++]; } @@ -101,6 +102,10 @@ static int tgc_load(struct TOPO_GROUP_CONNECT* gc) { size_t previous_count = gc->count; struct tgc_candidate* c = tgc_add(gc, peers[i].node_id); if (!c) { u_free(peers); return -1; } + if (c->priority != i) + DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "group candidate priority: group=%016llx node=%016llx position=%zu", + (unsigned long long)group->group_id, (unsigned long long)c->node_id, i); + c->priority = i; uint8_t device = peers[i].client_type; struct TOPO_NODE* node = topo_node_registry_find(group->instance->topo_groups, c->node_id); if (node && node->timestamp) device = node->client_type; @@ -283,7 +288,8 @@ static void tgc_step(void* arg) { for (size_t i = 0; i < gc->count; i++) { struct tgc_candidate* c = &gc->candidates[i]; if (!c->listed || c->kind != kind || c->request || c->retry_at > now || tgc_peer_up(gc->group, c->node_id)) continue; - if (!best || c->tested < best->tested || (c->tested == best->tested && c->retry_at < best->retry_at)) best = c; + if (!best || c->tested < best->tested || (c->tested == best->tested && + (c->retry_at < best->retry_at || (c->retry_at == best->retry_at && c->priority < best->priority)))) best = c; } if (!best) break; if (tgc_open(gc, best) == 0) pending[kind]++; diff --git a/src/routing_layer/topo_group_connect.h b/src/routing_layer/topo_group_connect.h index c3ea44fd..40060261 100644 --- a/src/routing_layer/topo_group_connect.h +++ b/src/routing_layer/topo_group_connect.h @@ -2,10 +2,21 @@ * Суперузел подключается ко всем суперузлам канала. Desktop/mobile — к двум; * без UP суперузлов резервируют два desktop, без них — два mobile. Проверки * старших классов продолжаются независимо от резервных связей. Роль supernode - * не зависит от адресов. История/RTT упорядочивают кандидатов внутри класса. + * не зависит от адресов; server без роли supernode и неизвестные устройства + * автоматически не выбираются. Класс устройства берётся из подписанной записи + * реестра или сохранённого snapshot, роль — из adm_tags локальной таблицы участников. + * В классе сначала идут ещё не проверенные кандидаты, затем готовые повторы + * с меньшим retry_at. При равенстве — текущий SQL-порядок: connected DESC, + * известный RTT перед NULL, RTT ASC, node_id ASC. Обновление списка сохраняет + * запросы/backoff известных кандидатов и применяет их новый SQL-приоритет. * Успех — транспортный UP; группа удерживает сессию до DOWN, обмен таблицами * продолжается отдельно. На класс не более двух попыток, CONNECTING: 2 s. - * Повторы с backoff 1..30 s или в standby burst. READY/recovery не изменяются. + * Автоповторы с backoff 1,2,4,8,16,30 s; проверка списка раз в 1 s, пока цель + * суперузлов не достигнута. Без UP всех суперузлов не переходим к desktop, + * пока каждый суперузел не проверен; mobile разрешён после проверки desktop + * и лишь при отсутствии UP обоих старших классов. Уже поднятые резервы сохраняются. + * В standby новые автоматические попытки ждут burst; ручная стартует сразу. + * READY/recovery не изменяются. * Планировщик владеет запросами, группа — NCD. Остановка отменяет попытки, * включая ручные, но сохраняет готовые сессии. Все вызовы — в потоке uasync. */ @@ -17,22 +28,30 @@ struct TOPO_GROUP; struct ETCP_CONN; -/* Включить/обновить автоматический подбор, сохранив текущие запросы. 0/-1. */ +/* Включить/обновить автоматический подбор живой CHAT-группы с channel_id. + * 0=проверка запланирована, -1=неверная группа/ошибка выделения контекста. + * Текущие запросы сохраняются; ошибка отложенного запуска логируется отдельно. */ int topo_group_connect_init(struct TOPO_GROUP* group); -/* Отменить запросы, таймеры и отложенные вызовы планировщика. */ +/* Отменить запросы, таймеры, standby-wait и отложенные вызовы; освободить group->connect. + * Если группа не stopping, её UP-пиры закрепляются до освобождения запросов. NULL допустим. */ void topo_group_connect_destroy(struct TOPO_GROUP* group); /* Запланировать проверку изменившихся сессий. */ void topo_group_connect_changed(struct TOPO_GROUP* group); /* Учесть появление пира; для UP записать connected в БД. */ void topo_group_connect_on_up(struct TOPO_GROUP* group, struct ETCP_CONN* conn); -/* Учесть потерю пира и продолжить подбор кандидатов. */ +/* Учесть потерю пира (connected=0 в БД) и продолжить подбор кандидатов. */ void topo_group_connect_on_down(struct TOPO_GROUP* group, struct ETCP_CONN* conn); /* Число текущих UP-пиров группы, независимо от READY. */ int topo_group_connect_active_count(struct TOPO_GROUP* group); -/* Сбросить backoff после изменения сети, сохранив UP и незавершённые запросы. */ +/* Сбросить backoff и standby-wait автоматического подбора после изменения сети; + * сохранить признак tested, UP и незавершённые запросы. Ручной режим не меняется. */ void topo_group_connect_restart(struct TOPO_GROUP* group); -/* Одна ручная попытка, общий запрос к сессии группы. 0=принято, -1=ошибка. */ +/* Одна ручная попытка к ненулевому node_id != self в живой CHAT-группе. + * Создаёт планировщик без автоподбора, если его ещё нет; проверяет членство через peer_open. + * 0=принято/запрос уже есть, -1=ошибка. Ручная попытка обходит класс/backoff/standby, + * но завершается по общему CONNECTING-таймауту и сама не повторяется. + * В автоматическом режиме этот же кандидат после завершения подчиняется обычной политике. */ int topo_group_connect_node_once(struct TOPO_GROUP* group, uint64_t node_id); #endif diff --git a/tests/test_group_connect.c b/tests/test_group_connect.c index cd312ba7..f8de210c 100644 --- a/tests/test_group_connect.c +++ b/tests/test_group_connect.c @@ -201,6 +201,18 @@ static void all_supernodes(void) { destroy(&f); } +/* Обновлённый SQL-порядок должен менять выбор уже известных кандидатов. */ +static void refreshed_priority(void) { + struct fixture f; create(&f, CLIENT_TYPE_DESKTOP, 0); start(&f); + fail(&f, 0); fail(&f, 1); fail(&f, 2); + assert(peer(&f, 3) && peer(&f, 4)); + assert(topo_node_sqlite_set_connected(f.inst->topo_sqlite_db, "42", f.ids[2], 1) == 0); + topo_node_sqlite_update_rtt(f.inst->topo_sqlite_db, f.ids[2], 0); + topo_group_connect_restart(f.group); poll_events(&f); + assert(peer(&f, 2) && peer(&f, 0) && !peer(&f, 1)); + destroy(&f); +} + /* Тип берётся из сохранённого snapshot; приватный адрес и пустые адреса не снимают роль. */ static void persisted_classification(void) { struct fixture f; create(&f, CLIENT_TYPE_DESKTOP, 0); @@ -264,6 +276,7 @@ int main(void) { fallback(CLIENT_TYPE_DESKTOP); fallback(CLIENT_TYPE_MOBILE); all_supernodes(); + refreshed_priority(); #ifdef UTUN_HAVE_STANDBY standby_down(); #endif