Reconcile CM protocol scheduling cache and cleanup contracts

This commit is contained in:
evgeny committed 2026-10-24 16:24:30 +02:00
1 parent 00a85a1468
commit 8e7fb1f31b
2 files changed
+58 -21

No files matched your search

+42 -8
View File
@@ -18,7 +18,8 @@
Доступность определяется по опубликованным адресам и общей классификации
`sock_match`: публичный IPv4, EIM/прямой NAT, пригодный глобальный IPv6.
Приватные адреса EIM-интерфейса не считаются внешними.
Приватные адреса EIM-интерфейса не считаются внешними. Для интерфейса с политикой
PUBLIC объявленный LAN-listener также разрешает сначала попробовать DIRECT.
DIRECT использует NCD, который создаёт UDP/TCP-линки по известной записи узла,
включая IPv4/IPv6, LAN и Reality-параметры. LOCAL — те же локальные handshake-пробы:
@@ -45,11 +46,9 @@ REVERSE просит получателя открыть/дополнить NCD
DIRECT/REVERSE определяются по текущим живым линкам: наличие исходящего означает
DIRECT, только входящие — REVERSE. Метка первого UP не используется как постоянный тип.
Окно звонка показывает фактический путь router, включая BGP fallback до готовности CM:
DIRECT/REVERSE/INDIRECT/NONE. Событие `CHAT_EVT_CALL_CONNECTION` содержит call_id,
тип и next hop; публикуется при изменении. Проверка идёт раз в секунду общим таймером
звонка, сразу при начале/принятии и при событиях CM. Завершение сбрасывает статус
и отменяет таймер. Отображение одинаково в Qt и Android.
`conn_mgr_route_conn()` возвращает заимствованный next hop подтверждённой доставки.
Его `allow_probe` разрешает использовать ещё не подтверждённый выбор только для
собственного управляющего обмена CM. ETCP-router может отдельно использовать BGP fallback.
Один timer на entry обслуживает ближайшее запланированное действие, повторы и
общий предел ожидания. READY и изменение маршрутов перепроверяют пути через
@@ -72,7 +71,8 @@ TIMEOUT и срок очередного цикла проб независим
Если потеряны все пути, сервис получает DOWN. Затем новый цикл запускает DIRECT
и LOCAL сразу, REVERSE через 0/500 мс; при отсутствии пригодного резерва INDIRECT
запускается сразу независимо от NAT. Рабочий посредник сохраняется во время проб.
Каждый цикл ограничен 15 секундами. Между неудачными циклами пауза растёт:
Дедлайн цикла — через 15 секунд от запланированного INDIRECT: 15–16 секунд
от начального старта, при recovery — 15 секунд. Между неудачными циклами пауза растёт:
1 → 2 → 4 → 8 → 15 секунд, далее остаётся 15 секунд. Подтверждённый прямой READY
останавливает поиск и сбрасывает паузу.
@@ -114,7 +114,34 @@ TIMEOUT и срок очередного цикла проб независим
Wire-структуры описаны в `conn_mgr_priv.h`. DIRECT_REQ содержит только request_id.
INTERM_SELECTED и INTERM_ACK содержат request_id и до трёх `{node_id, rtt}`.
Совместимость с прежним адресным DIRECT_REQ не поддерживается.
Структуры фиксированной длины, все числа native endian: DIRECT_REQ 6 байт,
EXCHANGE_REQ 37, EXCHANGE_RESP 68, SELECTED/ACK 37. Массивы на проводе всегда
содержат три слота; count указывает число действительных. RTT в единицах 0.1 мс.
request_id ненулевой; ноль пропускается при переполнении счётчика.
DIRECT_RESP и DISCONNECT удалены; неизвестные команды отклоняются с WARN.
Удалённый узел не может командой закрыть локальную потребность сервиса.
Добавление участников и получение описания канала — chat_join/member_sync и topo_group_invite.
## Фоновые проверки и освобождение
BG ping проверяет по одному узлу каждые 100 мс; полный цикл начинается не чаще
одного раза в 60 секунд. Узлы с живым прямым линком пропускаются. Выбирается
первый подходящий UDP endpoint IPv4/IPv6. Candidate ping работает каждые 2 секунды:
обновляет до трёх кандидатов, пингует активные UDP/STCP-линки и запускает пробы
route_connectivity для неактивных. Без активного линка устаревший замер удаляет
кандидата через 30 секунд; проверка свежести RTT для согласования использует 20 секунд.
NAT-детектор откладывает эти пробы, standby переносит их в активную фазу.
Кандидаты упорядочены по RTT, затем node_id. При заполнении кеша кандидат с
READY-сессией вытесняет недоступного независимо от его старого RTT. Проба
недоступного узла не вытесняет READY-кандидата из полностью рабочего списка.
Перед согласованием каждое плечо и RTT всё равно проверяются заново.
close/cleanup сразу снимают объект с рабочего списка и отменяют callbacks.
Память закрытых handles/entries остаётся у менеджера до начала фонового callback
или destroy: вложенные callbacks могут безопасно закрывать себя и следующий handle.
Сборка не выделяет память и не зависит от успешного call_soon при закрытии.
Destroy отсоединяет ещё открытые handles; они остаются у вызывающего до close.
## API и владение
@@ -132,6 +159,10 @@ INTERM_SELECTED и INTERM_ACK содержат request_id и до трёх `{nod
NCD-владение CM. Группа и другие NCD-владельцы сохраняют транспорт.
- `conn_mgr_get_conn()` возвращает заимствованный прямой транспорт либо NULL
при INDIRECT/отсоединённом handle. Данные следует отправлять через router.
Сервис передаёт собственный svc_id и выбирает шифрование; общего conn_mgr_send нет.
- Загрузка узла из SQLite временно удерживает запись реестра. Ошибки регистрации,
группового узла, CM-entry или handle освобождают созданные объекты. Приобретение
handle уже готового пути возвращает ошибку, если отложенный UP не удалось запланировать.
Диагностика CM — категория `general`: INFO показывает расписание, выбор и смену
пути с временем от старта, новые циклы и паузы; DEBUG — события NCD, RTT и обработку ответов; WARN/ERROR —
@@ -144,3 +175,6 @@ INTERM_SELECTED и INTERM_ACK содержат request_id и до трёх `{nod
обрыв после начального срока, повторные циклы с реальными паузами 1/2/4с, смену адресов
и локальных сокетов, потерю маршрута посредника, устаревший ACK, начальный TIMEOUT
с последующим UP и закрытие последнего handle из TIMEOUT callback.
`test_conn_mgr_handles` дополнительно проверяет READY-кандидата при заполненном
недоступными узлами кеше, отказ начального UP и close без аллокаций; `test_topo_node_wire`
проверяет четыре отказа bootstrap и ошибки обязательных CM-таймеров.
+16 -13
View File
@@ -38,7 +38,7 @@ struct stcp_client;
#define CONN_MGR_EXCHANGE_RETRANSMIT_MS 1000
#define CONN_MGR_RETRY_MIN_MS 1000
#define CONN_MGR_RETRY_MAX_MS 15000
#define CACHE_MS 20000 /* время жизни RTT-замера ~20с (через cm_is_rtt_fresh, мс) */
#define CACHE_MS 20000 /* свежесть RTT-пробы 20с; поля *_tb и RTT используют 0.1мс */
#define CONN_MGR_MAX_INTERMEDIARIES 3
/* ═══════ состояния ═══════ */
@@ -47,7 +47,6 @@ enum CONN_MGR_STATE {
CONN_MGR_STATE_DISCONNECTED = 0,
CONN_MGR_STATE_CONNECTING = 1,
CONN_MGR_STATE_CONNECTED = 2,
CONN_MGR_STATE_FAILED = 3,
};
enum CM_TRY_STATE {
@@ -67,8 +66,12 @@ enum {
CM_SUBCMD_INTERM_ACK = 0x07,
};
/* RTT — 0.1мс; 0/FFFF не участвуют в согласовании. */
struct CONN_MGR_CANDIDATE { uint64_t node_id; uint16_t rtt; } __attribute__((packed));
/* Полные фиксированные структуры, включая неиспользованные слоты массивов.
* Native endian, cmd=ETCP_RT_ID_CONN_MGR; request_id ненулевой.
* DIRECT_REQ=6B, EXCHANGE_REQ=37B, EXCHANGE_RESP=68B, SELECTED/ACK=37B. */
#pragma pack(push, 1)
struct CM_DIRECT_REQ { uint8_t cmd, subcmd; uint32_t request_id; };
struct CM_EXCHANGE_REQ { uint8_t cmd, subcmd; uint32_t request_id; uint8_t candidate_count; struct CONN_MGR_CANDIDATE candidates[CONN_MGR_MAX_CANDIDATES]; };
@@ -84,7 +87,7 @@ struct CM_INTERM_SEL { uint8_t cmd, subcmd; uint32_t request_id; uint8_t coun
/* ═══════ внутренние структуры ═══════ */
struct CONN_MGR_HANDLE {
uint64_t node_id; uint64_t group_id; conn_mgr_cb_t cb; void* cb_arg;
uint64_t node_id; uint64_t group_id; conn_mgr_cb_t cb; void* cb_arg; /* исходная цель/группа, заимствованный callback */
struct CONN_MGR_ENTRY* entry; struct CONN_MGR_HANDLE* next;
struct CONN_MGR* mgr; /* владелец памяти до destroy; NULL у отсоединённого после destroy handle */
struct CONN_MGR_HANDLE* owner_next; /* список всех handles mgr либо список закрытых до безопасного GC */
@@ -96,19 +99,19 @@ struct CONN_MGR_HANDLE {
struct CONN_MGR_ENTRY {
struct ll_entry ll; /* ll_queue entry — первый элемент */
uint64_t node_id; /* хеш-ключ 8B, offset от data = 0 */
uint8_t state, conn_type;
uint64_t last_traffic_tb;
struct CONN_MGR_HANDLE* handles; struct NODE_CONN_DIRECT* ncd_handle;
uint64_t intermediaries[3]; uint8_t intermediariy_count;
uint8_t local_scan_state, main_connect_state, db_loaded;
uint8_t state, conn_type; /* CONN_MGR_STATE / CONN_TYPE; рабочая доставка независимо от проб */
uint64_t last_traffic_tb; /* последняя принятая CM-команда при CONNECTED; не idle-дедлайн */
struct CONN_MGR_HANDLE* handles; struct NODE_CONN_DIRECT* ncd_handle; /* владельцы запроса; отдельный общий прямой транспорт CM */
uint64_t intermediaries[3]; uint8_t intermediariy_count; /* подтверждённый резерв, актуальность проверяется перед выбором */
uint8_t local_scan_state, main_connect_state, db_loaded; /* CM_TRY / CM_TRY / временный узел из SQLite */
uint8_t reverse_started, indirect_started, indirect_confirmed, direct_up, timeout_notified, ever_up;
uint8_t attempt_active;
uint16_t retry_delay_ms;
void* timer; void* refresh_token;
uint8_t attempt_active; /* исходящий цикл; не признак CONNECTED */
uint16_t retry_delay_ms; /* пауза между циклами, 1000..15000мс */
void* timer; void* refresh_token; /* ближайший шаг/срок; отложенный пересчёт после маршрутов */
uint64_t start_tb, reverse_start_tb, indirect_start_tb, deadline_tb;
uint64_t initial_deadline_tb, retry_tb, node_timestamp, peer_exchange_deadline_tb;
uint64_t reverse_next_tb, reverse_deadline_tb, indirect_next_tb;
uint32_t reverse_request_id, exchange_request_id, peer_exchange_request_id, peer_reverse_request_id;
uint32_t reverse_request_id, exchange_request_id, peer_exchange_request_id, peer_reverse_request_id; /* независимые исходящие/входящие id */
struct CM_INTERM_SEL selection; /* Исходящий выбор до подтверждения пира. */
struct CONN_MGR* mgr;
struct CONN_MGR_ENTRY* retired_next; /* уже снятая с entries запись до безопасного GC */
@@ -121,7 +124,7 @@ struct CONN_MGR {
struct CONN_MGR_ENTRY* retired_entries; /* сборка в фоновых callbacks/destroy не требует выделений памяти */
void *bg_ping_timer, *candidate_ping_timer;
void *bg_ping_wait, *candidate_ping_wait; /* standby_wait handle (Android) */
size_t bg_ping_cursor; uint64_t bg_ping_cycle_start_tb;
size_t bg_ping_cursor; uint64_t bg_ping_cycle_start_tb; /* один узел за тик; полный цикл не чаще 60с */
struct CONN_MGR_CANDIDATE best_candidates[CONN_MGR_MAX_CANDIDATES]; uint8_t best_candidate_count;
struct stcp_client* cand_tcp_pings[CONN_MGR_MAX_CANDIDATES]; /* влётные candidate TCP-пинги (для отмены при destroy) */
uint32_t next_request_id; uint8_t initialized;