Separate core, UTUN and chat lifecycles with owned asynchronous work

This commit is contained in:
evgeny committed 2026-09-28 11:39:08 +03:00
1 parent 5838e63cde
commit 72fee9e245
47 files changed
+708 -337

No files matched your search

+26 -4
View File
@@ -1356,18 +1356,18 @@ void etcp_router_on_send_ready(struct UTUN_INSTANCE* inst, uint64_t group_id, ui
struct queue_waiter_handle* h,
queue_threshold_callback_fn callback, void* arg) {
if (!inst || !h) return;
etcp_router_cancel_send_ready(inst, group_id, node_id, svc_id, h);
struct ETCP_ROUTER_CONN* rconn = router_conn_find(inst, group_id, node_id, svc_id);
if (!rconn || !rconn->send_q) return;
h->defer_q = rconn->send_q; /* Keep the registration queue across route/session replacement. */
queue_waiter_wait(rconn->send_q, h, callback, arg);
}
// Backpressure: отменить waiter на send_q для (group_id, node_id, svc_id).
void etcp_router_cancel_send_ready(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t node_id, uint8_t svc_id,
struct queue_waiter_handle* h) {
if (!inst || !h) return;
struct ETCP_ROUTER_CONN* rconn = router_conn_find(inst, group_id, node_id, svc_id);
if (!rconn || !rconn->send_q) return;
queue_waiter_cancel(rconn->send_q, h);
(void)inst; (void)group_id; (void)node_id; (void)svc_id;
if (h && (h->internal || h->call_soon_id)) queue_waiter_cancel(h->defer_q, h);
}
// Backpressure: есть ли место в send_q (без регистрации waiter). 1 — есть, 0 — нет.
@@ -1466,3 +1466,25 @@ void etcp_router_conn_close_all_for_node(struct UTUN_INSTANCE* inst, uint64_t gr
for (int i = 0; i < count; i++)
router_close_and_notify((struct ETCP_ROUTER_CONN*)to_close[i]);
}
/* Service stop: discard every logical channel in a group before its routes disappear.
* Call after service producers have cancelled their waiters. */
void etcp_router_close_group(struct UTUN_INSTANCE* inst, uint64_t group_id) {
if (!inst) return;
/* Notifications may close other channels too; do not retain list cursors across them. */
while (inst->router_conns) {
struct ll_entry* entry = inst->router_conns->head;
while (entry && ((struct ETCP_ROUTER_CONN*)entry)->group_id != group_id) entry = entry->next;
if (!entry) break;
router_close_and_notify((struct ETCP_ROUTER_CONN*)entry);
}
for (struct ll_entry* e = inst->connections ? inst->connections->head : NULL; e; e = e->next) {
struct ETCP_CONN* conn = ((struct conn_queue_entry*)e->data)->conn;
if (!conn) continue;
struct ll_entry* entry = conn->transit_queues ? conn->transit_queues->head : NULL;
while (entry) {
struct TRANSIT_QUEUE* transit = (struct TRANSIT_QUEUE*)entry; entry = entry->next;
if (transit->group_id == group_id) transit_queue_destroy(conn, transit);
}
}
}
+1
View File
@@ -253,6 +253,7 @@ int etcp_router_send_q_has_room(struct UTUN_INSTANCE* inst, uint64_t group_id, u
void etcp_router_pause_retrans_for_node(struct UTUN_INSTANCE* inst, uint64_t remote_node_id);
// Закрыть все router_conn для указанного (group_id, remote_node_id) (peer умер)
void etcp_router_close_group(struct UTUN_INSTANCE* inst, uint64_t group_id);
void etcp_router_conn_close_all_for_node(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t remote_node_id);
// Начать новую локальную сессию для конкретного peer+svc в группе и уведомить сервис.
+1 -1
View File
@@ -281,7 +281,7 @@ void routing_destroy(struct UTUN_INSTANCE* instance) {
(unsigned long long)instance->node_id);
// Unbind DATA handler from etcp_router
etcp_router_unbind(instance, ETCP_RT_ID_DATA);
if (instance->router_bindings.callbacks[ETCP_RT_ID_DATA]) etcp_router_unbind(instance, ETCP_RT_ID_DATA);
// Clean up route table if exists
if (instance->rt) {
+26 -19
View File
@@ -563,6 +563,16 @@ static void topo_group_destroy(struct TOPO_GROUP* group) {
radio_destroy(group);
DEBUG_INFO(DEBUG_CATEGORY_SYS, "[GRP_DESTROY] 2c radio_destroy done");
/* A stopped service must withdraw membership even if another group keeps ETCP alive. */
if (group->instance->running) {
struct ll_entry* current = group->senders_list->head;
while (current) {
struct TOPO_GROUP_CONN_ITEM* peer = (struct TOPO_GROUP_CONN_ITEM*)current->data;
current = current->next;
if (peer->conn && peer->conn->links_up) topo_group_leave_peer(group, peer->handle);
}
}
struct ll_entry* e;
while ((e = queue_data_get(group->senders_list)) != NULL) {
struct TOPO_GROUP_CONN_ITEM* item = (struct TOPO_GROUP_CONN_ITEM*)e->data;
@@ -574,6 +584,7 @@ static void topo_group_destroy(struct TOPO_GROUP* group) {
queue_free(group->senders_list);
DEBUG_INFO(DEBUG_CATEGORY_SYS, "[GRP_DESTROY] 3 senders_list done");
etcp_router_close_group(group->instance, group->group_id);
if (group->conn_mgr) { conn_mgr_destroy(group->conn_mgr); group->conn_mgr = NULL; }
DEBUG_INFO(DEBUG_CATEGORY_SYS, "[GRP_DESTROY] 4 conn_mgr done");
@@ -589,6 +600,7 @@ static void topo_group_destroy(struct TOPO_GROUP* group) {
if (nc) DEBUG_INFO(DEBUG_CATEGORY_SYS, "[GRP_DESTROY] 4b nodes cleaned (%d)", nc);
}
if (group->local_node) {
route_delete(group->instance->rt, group->local_node);
if (group->local_node->paths) { queue_free(group->local_node->paths); group->local_node->paths = NULL; }
topo_nodeq_free_group_fields(group->instance->topo_groups, group->local_node);
u_free(group->local_node);
@@ -601,15 +613,16 @@ static void topo_group_destroy(struct TOPO_GROUP* group) {
DEBUG_INFO(DEBUG_CATEGORY_SYS, "[GRP_DESTROY] 5 done");
}
/* Удаляет группу из контейнера (кроме UTUN-группы по умолчанию). */
/* Удаляет группу и все принадлежащие ей ресурсы. */
void topo_groups_remove_group(struct TOPO_GROUPS* g, uint64_t group_id) {
if (!g || !g->group_list || group_id == TOPO_GROUP_UTUN) return;
if (!g || !g->group_list) return;
struct TOPO_GROUP* group = topo_groups_find(g, group_id);
if (!group) { DEBUG_WARN(DEBUG_CATEGORY_BGP, "remove_group: group %016llx not found", (unsigned long long)group_id); return; }
DEBUG_INFO(DEBUG_CATEGORY_BGP, "remove_group: id=%016llx type=%d ch=%s",
(unsigned long long)group_id, group->group_type, group->channel_id);
if (g->instance->conn_mgr == group->conn_mgr) g->instance->conn_mgr = NULL;
queue_remove_data(g->group_list, &group->ll);
topo_group_destroy(group); /* node_cbks освобождаются внутри */
queue_entry_free(&group->ll);
@@ -637,9 +650,10 @@ struct TOPO_GROUPS* topo_groups_init(struct UTUN_INSTANCE* instance) {
g->v4_subnet_pool = memory_pool_init(sizeof(struct TOPO_SUBNET4), "to_v4sub");
g->v6_subnet_pool = memory_pool_init(sizeof(struct TOPO_SUBNET6), "to_v6sub");
if (instance->config && instance->config->global.db_path[0]) {
char db_file[512];
snprintf(db_file, sizeof(db_file), "%s/chats.db", instance->config->global.db_path);
{
char db_file[512] = ":memory:";
if (instance->config && instance->config->global.db_path[0])
snprintf(db_file, sizeof(db_file), "%s/chats.db", instance->config->global.db_path);
int rc = sqlite3_open_v2(db_file, &instance->topo_sqlite_db,
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, NULL);
if (rc == SQLITE_OK && instance->topo_sqlite_db) {
@@ -661,18 +675,7 @@ struct TOPO_GROUPS* topo_groups_init(struct UTUN_INSTANCE* instance) {
instance->topo_groups = g;
struct TOPO_GROUP* default_group = topo_group_create(instance, TOPO_GROUP_UTUN, TOPO_GROUP_TYPE_UTUN);
if (!default_group) {
memory_pool_destroy(g->v4_sock_meta_pool); memory_pool_destroy(g->v4_addr_pool);
memory_pool_destroy(g->v6_sock_meta_pool); memory_pool_destroy(g->v6_addr_pool);
memory_pool_destroy(g->v4_subnet_pool); memory_pool_destroy(g->v6_subnet_pool);
queue_free(g->node_registry); queue_free(g->group_list); u_free(g);
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "failed to create default group");
return NULL;
}
queue_data_put_with_index(g->group_list, &default_group->ll);
instance->conn_mgr = default_group->conn_mgr; /* recv handler shortcut */
if (topo_node_init_self(instance) < 0) { topo_groups_destroy(instance); return NULL; }
etcp_bind(instance, ETCP_ID_TOPO_ENTRY, topo_group_receive_cbk);
etcp_add_conn_status_cbk(instance, topo_group_conn_status, g);
@@ -680,7 +683,7 @@ struct TOPO_GROUPS* topo_groups_init(struct UTUN_INSTANCE* instance) {
ETCP_SOCKET_EVENT_ADDR_CHANGED | ETCP_SOCKET_EVENT_STATUS_CHANGED);
utun_add_activity_cbk(instance, topo_group_on_activity_change, NULL);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "TOPO groups initialized (with default group)");
DEBUG_INFO(DEBUG_CATEGORY_BGP, "TOPO registry initialized (services create their own groups)");
return g;
}
@@ -1655,8 +1658,12 @@ static void topo_leave_event(struct NODE_CONN_DIRECT* handle, enum ncd_event eve
}
static void topo_leave_ready(struct ll_queue* queue, void* arg) {
(void)queue;
struct topo_leave* leave = arg;
if (queue_entry_count(queue)) {
if (queue_waiter_wait(queue, &leave->waiter, topo_leave_ready, leave) >= 0) return;
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "cannot rearm LEAVE_GROUP waiter");
topo_leave_finish(leave); return;
}
struct ETCP_CONN* conn = node_conn_direct_get_conn(leave->handle);
struct TOPO_GROUP* group = topo_groups_find(leave->instance->topo_groups, leave->group_id);
if (group && topo_group_has_sender(group, conn)) {
+2 -2
View File
@@ -281,8 +281,8 @@ struct TOPO_GROUP* topo_groups_create_group(struct TOPO_GROUPS* g, uint64_t grou
* @brief Удаляет одну группу по id (разрушает conn_mgr, connect-цикл, broadcast,
* recovery, узлы и подписчиков node_cbks) и извлекает её из group_list.
*
* Используется при локальном удалении чат-канала. Группу по умолчанию
* (TOPO_GROUP_UTUN) удалять нельзя — функция это игнорирует.
* Используется при удалении канала и остановке сервиса. UTUN-группой
* управляет utun_service_start/stop.
*/
void topo_groups_remove_group(struct TOPO_GROUPS* g, uint64_t group_id);
+3 -4
View File
@@ -122,8 +122,7 @@ static void tgi_handle_info_req(struct ETCP_CONN* conn, const uint8_t* data, siz
char ch_id[32]; snprintf(ch_id, sizeof(ch_id), "%llu", (unsigned long long)req->group_id);
struct TOPO_GROUP* grp = topo_groups_find(inst->topo_groups, req->group_id);
struct TOPO_GROUP* sg = grp ? grp : topo_groups_get_default(inst->topo_groups);
struct TOPO_NODE* ln = sg && sg->local_node ? topo_node_registry_find(inst->topo_groups, sg->local_node->node_id) : NULL;
struct TOPO_NODE* ln = topo_node_registry_find(inst->topo_groups, inst->node_id);
const char* nm = ln ? ln->node_name : NULL; size_t nl = nm ? strlen(nm) : 0; if (nl > 255) nl = 255;
sqlite3* db = inst->topo_sqlite_db;
@@ -138,7 +137,7 @@ static void tgi_handle_info_req(struct ETCP_CONN* conn, const uint8_t* data, siz
struct TGI_INVITE_RESP* resp = (struct TGI_INVITE_RESP*)rb;
resp->cmd = ETCP_RT_ID_GROUP_INVITE; resp->subcmd = TGI_SUBCMD_INFO_RESP;
resp->group_id = req->group_id; resp->is_member = 0;
if (sg) memcpy(resp->ed25519_pubkey, sg->ed25519_public_key, SC_PUBKEY_SIZE);
memcpy(resp->ed25519_pubkey, inst->my_ed25519_pubkey, SC_PUBKEY_SIZE);
resp->node_name_len = (uint8_t)nl; if (nl) memcpy(resp->node_name, nm, nl);
struct ll_entry* qe = queue_entry_new(0);
if (qe) { qe->dgram = rb; qe->len = (uint16_t)rs;
@@ -207,7 +206,7 @@ static void tgi_handle_info_req(struct ETCP_CONN* conn, const uint8_t* data, siz
{ struct TGI_INVITE_RESP* resp = (struct TGI_INVITE_RESP*)rb;
resp->cmd = ETCP_RT_ID_GROUP_INVITE; resp->subcmd = TGI_SUBCMD_INFO_RESP;
resp->group_id = req->group_id; resp->is_member = 1;
if (sg) memcpy(resp->ed25519_pubkey, sg->ed25519_public_key, SC_PUBKEY_SIZE);
memcpy(resp->ed25519_pubkey, inst->my_ed25519_pubkey, SC_PUBKEY_SIZE);
resp->node_name_len = (uint8_t)nl; if (nl) memcpy(resp->node_name, nm, nl); }
uint8_t* ch = rb + TGI_INVITE_RESP_HDR_SIZE + nl;
+1 -1
View File
@@ -58,7 +58,7 @@ int topo_group_invite_join(struct UTUN_INSTANCE* inst, uint64_t group_id,
* Инвайтера сторона: прямое подключение (ncd) к целевому узлу для последующей
* отправки CHANNEL_INVITE (через chat_sync, по ETCP_CONN_STATUS_UP).
* Без INVITE_INFO-проверки членства. ni может быть NULL — тогда адреса грузятся
* из БД (node_conn_direct_open). Handle сохраняется в nq->handle.
* из БД. Группа владеет NCD; инициатор держит отменяемый групповой запрос.
*/
int topo_group_invite_to_channel(struct UTUN_INSTANCE* inst, uint64_t group_id,
struct TOPO_NODE* ni, uint64_t node_id);
+20 -2
View File
@@ -192,6 +192,7 @@ void topo_nodeq_free_group_fields(struct TOPO_GROUPS* groups, struct TOPO_GROUP_
void topo_nodeq_remove_node(struct TOPO_GROUP* group, struct TOPO_GROUP_NODE* nq) {
if (!group || !nq) return;
route_connectivity_cancel_node(nq);
if (group->instance) route_delete(group->instance->rt, nq);
if (nq->paths) { queue_free(nq->paths); nq->paths = NULL; }
queue_remove_data(group->nodes, &nq->ll);
topo_nodeq_free_group_fields(group->instance ? group->instance->topo_groups : NULL, nq);
@@ -928,6 +929,25 @@ int topo_node_format_all(struct TOPO_GROUP* group, char* buf, size_t buf_size) {
// ===== update my nodeinfo =====
/* Core owns one reference independently of all services. */
int topo_node_init_self(struct UTUN_INSTANCE* instance) {
struct TOPO_NODE* ni = u_calloc(1, sizeof(*ni));
if (!ni) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "local identity allocation failed"); return -1; }
ni->node_id = instance->node_id; ni->ver = 1;
ni->client_type = instance->client_type; ni->client_activity = instance->client_activity;
memcpy(ni->public_key, instance->my_keys.public_key, SC_PUBKEY_SIZE);
memcpy(ni->ed25519_public_key, instance->my_ed25519_pubkey, SC_PUBKEY_SIZE);
ni->node_name = u_strdup(instance->name);
if (!ni->node_name || topo_node_sign_self(instance, ni) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "cannot initialize local identity");
topo_node_destroy(instance->topo_groups, ni); return -1;
}
if (!topo_node_registry_store(instance->topo_groups, ni)) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "cannot register local identity"); return -1;
}
return 0;
}
/* Пересоздаёт local_node группы при изменении имени/подсетей/ключей, инкрементируя версию. */
int topo_group_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct TOPO_GROUP* group) {
if (!instance || !group) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "invalid args"); return -1; }
@@ -1039,8 +1059,6 @@ int topo_group_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct TOPO_GR
/* Пересобирает списки адресов своего узла из ETCP-сокетов; при изменении — подпись, БД, рассылка. */
int topo_node_update_my_addresses(struct UTUN_INSTANCE* instance) {
if (!instance || !instance->topo_groups) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "invalid args"); return -1; }
struct TOPO_GROUP* default_group = topo_groups_get_default(instance->topo_groups);
if (!default_group || !default_group->local_node) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "no local_node"); return -1; }
struct TOPO_NODE* ni = topo_node_registry_find(instance->topo_groups, instance->node_id);
if (!ni) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "my node not in registry"); return -1; }
+1
View File
@@ -243,6 +243,7 @@ int topo_node_deserialize(struct TOPO_GROUP* group, const uint8_t* data, size_t
uint64_t* topo_node_best_hop_list(struct TOPO_GROUP_NODE* nq, uint8_t* out_count, uint16_t* out_rtt);
int topo_group_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct TOPO_GROUP* group);
int topo_node_init_self(struct UTUN_INSTANCE* instance);
int topo_node_update_my_addresses(struct UTUN_INSTANCE* instance); /* 1=changed, 0=unchanged, -1=error */
void topo_node_on_socket_changed(struct ETCP_SOCKET* sock, int event, void* arg);
void topo_node_dump_all(struct TOPO_GROUP* group);