# 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 (приемлемо).