- u_async.c: process immediate_queue BEFORE epoll_wait in uasync_poll - stcp_link.c: defensive NULL etcp_conn/etcp_link in stcp_link_close - utun_instance.c: move stcp_server_list_destroy_all to Phase J (before ETCP), add deferred drain in both Phase J and L - stcp.h/c: STCP_HS_ENC_CLIENT 6→38, STCP_HS_ENC_SERVER 7→39, exchange ed25519_pubkey in both handshake directions - etcp_connections.c: copy peer_ed25519 from STCP to ETCP_CONN in etcp_link_enter_ready_tcp; guard link->etcp in tcp_link_close_cb - chat_sync.c: fix lk->conn NULL dereference for TCP links - test_stcp.c: update stcp_server_create/stcp_client_connect callsites - tools/chat_tcp_test: change transport udp→tcp
68 lines
2.8 KiB
Plaintext
68 lines
2.8 KiB
Plaintext
# cm_invite_pending — утечка указателей и corruption при destroy
|
||
|
||
## Корень проблемы
|
||
|
||
У `cm_invite_pending` три внешних держателя указателя в разных подсистемах:
|
||
|
||
uasync heap (overall_timer) → node->arg = inv
|
||
NCD (node_conn_direct) → arg = inv в cm_invite_ncd_callback
|
||
STCP (stcp_link) → arg = inv в cm_tcp_ready_cb
|
||
|
||
Когда cm_invite_cancel делает u_free(inv), коллбэки от уже запланированных
|
||
событий приходят после освобождения → пишут в переиспользованную память →
|
||
corruption (float-значения в полях mgr/node_id).
|
||
|
||
## Решение
|
||
|
||
1. Поле uint8_t cancelled:1 в struct cm_invite_pending
|
||
(рядом с tcp_ready:1 в conn_mgr_priv.h)
|
||
|
||
2. cm_invite_cancel:
|
||
- inv->cancelled = 1 первой же строкой
|
||
- отмена таймера, закрытие ncd/tcp handles
|
||
- удаление из invite_list
|
||
- uasync_post(ua, cm_invite_free_deferred, inv) вместо немедленного u_free
|
||
|
||
3. Все коллбэки — guard первой строкой:
|
||
void cm_invite_ncd_callback(..., void* arg) {
|
||
struct cm_invite_pending* inv = arg;
|
||
if (inv->cancelled) return;
|
||
...
|
||
}
|
||
|
||
4. cm_invite_fail:
|
||
- if (inv->cancelled) return
|
||
- сохранить cb/handle/node_id/group_id ДО вызова cm_invite_cancel
|
||
- cm_invite_cancel(inv) (cancelled=1, deferred free)
|
||
- cb(handle, ...) (коллбэк после cancel, inv ещё жив)
|
||
- u_free(handle)
|
||
|
||
## Почему корректно
|
||
|
||
cm_invite_cancel:
|
||
inv->cancelled = 1 ← блокировка коллбэков (однопоточно, немедленно)
|
||
uasync_cancel_timeout ← таймер удалён из heap
|
||
node_conn_direct_close ← handle закрыт
|
||
uasync_post(free, inv) ← освобождение в конец очереди событий
|
||
|
||
[уже запланированные коллбэки]:
|
||
if (inv->cancelled) return ← выход без действий
|
||
|
||
[deferred free]:
|
||
u_free(inv) ← все коллбэки уже отработали
|
||
|
||
## Краевые случаи
|
||
|
||
conn_mgr_destroy → cm_invite_cancel:
|
||
Отмена таймера, закрытие handles, deferred free. Ни один коллбэк
|
||
не дёрнет освобождённую память.
|
||
|
||
NCD/STCP коллбэк после cancel:
|
||
cancelled=1 → мгновенный return.
|
||
|
||
Таймаут приглашения после cancel:
|
||
cancelled=1 → return, не вызывает cm_invite_fail повторно.
|
||
|
||
uasync_destroy до deferred free:
|
||
inv утекает при shutdown (приемлемо).
|