diff --git a/doc/service_lifecycle.md b/doc/service_lifecycle.md index 7f8e089e..56919360 100644 --- a/doc/service_lifecycle.md +++ b/doc/service_lifecycle.md @@ -46,11 +46,13 @@ SQLite открывается ядром один раз: `db_path/chats.db`, л на общих соединениях. Транзит других групп сохраняется. Отложенный LEAVE может временно удерживать транспорт самостоятельно, не обращаясь к уже удалённой группе. -ETCP удаляет транспорт после 30 секунд непрерывного DOWN, независимо от NCD handles. -Первоначальное ожидание UP тоже ограничено этим сроком. Повторный DOWN не продлевает -таймаут; восстановление UP отменяет его. Владельцы получают CLOSED, внешние ссылки -задерживают только освобождение памяти. Освобождение последнего handle во время DOWN -оставляет транспорт до его таймаута; явный force-close и отмена неудачной попытки закрывают сразу. +ETCP удаляет транспорт после 30 секунд непрерывного DOWN только при отсутствии локальных NCD handles. +Первоначальное ожидание UP тоже учитывает владение. Повторный DOWN не продлевает +таймаут; восстановление UP отменяет его. Если срок истёк при наличии handles, транспорт +сохраняется, а исходный deadline остаётся. Освобождение последнего handle во время DOWN +оставляет транспорт до этого deadline либо запускает удаление в следующем callback, +если срок уже истёк. Внешние ETCP refs задерживают только освобождение памяти. +Явный force-close и отмена неудачной попытки последнего владельца закрывают сразу. Входящий UP без локальных handles сохраняется: он может быть нужен пиру. CLOSE/KEEP_ALIVE привязан при запуске ядра, поэтому мягкое закрытие работает и на чисто входящем узле. @@ -64,7 +66,7 @@ KEEP_ALIVE полностью отменяет FIN_WAIT. Ожидание CLOSE объявленного остатка ACTIVE+SLEEP с запасом normal timeout. Фазовые таймеры и наши отправки срок жизни пира не продлевают. Пробуждение сразу сокращает deadline до normal timeout. После истечения последнего линка транспорт -становится DOWN, затем удаляется через 30 секунд. `test_etcp_server_lifecycle` +становится DOWN, затем может удаляться через 30 секунд, когда локальных NCD handles нет. `test_etcp_server_lifecycle` проверяет сроки, несколько путей, восстановление, владение, сон и CLOSE/KEEP_ALIVE с управляемыми часами и контролем освобождения памяти/таймеров. diff --git a/tests/test_conn_mgr_phases.c b/tests/test_conn_mgr_phases.c index 9e4fdf74..e99c5bdf 100644 --- a/tests/test_conn_mgr_phases.c +++ b/tests/test_conn_mgr_phases.c @@ -440,6 +440,18 @@ static int run_upgrade(void) { } poll_ms(10); if (ev_b != CONN_EVENT_UP) { fail("UPGRADE: incoming owner did not receive UP"); return -1; } + // Проба DIRECT в DOWN живёт дольше ETCP timeout: CM удерживает настоящий NCD handle. + struct ETCP_CONN* retained = node_conn_direct_get_conn(original); + if (!retained || retained->links_up || node_conn_direct_handle_count(retained) < 1) { + fail("UPGRADE: missing NCD ownership of DOWN direct probe"); return -1; + } + poll_ms(ETCP_DOWN_TIMEOUT_TB / 10 + 1000); + if (entry->ncd_handle != original || node_conn_direct_get_conn(original) != retained || retained->close_requested || + retained->state == 2 || node_conn_direct_handle_count(retained) < 1 || entry->conn_type != CONN_TYPE_INDIRECT || + up_count != 1 || down_count || timeout_count) { + fail("UPGRADE: ETCP timeout removed CM-owned direct probe or interrupted relay"); return -1; + } + DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "CM retains same NCD/ETCP while DIRECT is DOWN beyond timeout; INDIRECT remains available"); // Настоящий звонок использует тот же путь; GUI получает отдельные события, без повторных CM UP. if (call_init(inst_a) || call_init(inst_b)) { fail("CALL PATH: service initialization failed"); return -1; } chat_event_set_handler(inst_a, call_event_recv); chat_event_set_handler(inst_b, call_event_recv);