Serialize bridge command publication and report rejected lifecycle requests

This commit is contained in:
evgeny committed 2026-10-29 13:35:41 +01:00
1 parent 8374c60b0e
commit f731ac340d
3 files changed
+57 -106

No files matched your search

+5 -4
View File
@@ -84,10 +84,11 @@ Payload копируется в byte array; его формат зависит
Перед stop/restart Kotlin сбрасывает controllers и останавливает внешние media workers.
Worker снимает media-привязки, уничтожает instance, снимает standby/UDP-таймеры и уничтожает uasync.
`instance_lite_stop()` ждёт до двух секунд: при timeout отсоединяет поток, поэтому возврат
не гарантирует завершённый cleanup. Без опубликованного instance он возвращается сразу.
Restart пересоздаёт instance и uasync. Дополнительный опрос is_running в bridge до 5с
может увидеть ещё старое работающее ядро; подтверждение нового запуска — SERVICE_STARTED.
`instance_lite_stop()` ждёт полный cleanup и pthread_join, включая startup до публикации instance;
каждые две секунды ожидания пишет предупреждение, поток не отсоединяет. Повторный stop безопасен.
Restart пересоздаёт instance и uasync. Возврат restart/updateSockets подтверждает принятие копии INI,
ошибка очереди возвращается Kotlin; готовность нового запуска подтверждается SERVICE_STARTED.
Fatal signal handlers принадлежат платформе: worker не заменяет их обработчиком с небезопасными locks/JNI/backtrace.
Общие правила освобождения: `../../../doc/service_lifecycle.md`.
## Данные и конфигурация
@@ -259,13 +259,11 @@ int utun_bridge_send_message(const char* channel_id, const char* text, const uin
void utun_bridge_create_channel(const char* name, const char* channel_id) {
bridge_log(BLEV_INFO, "create_channel name=%s id=%s", name ? name : "", channel_id ? channel_id : "");
if (!name || !name[0]) { bridge_log(BLEV_ERROR, "create_channel: empty name"); return; }
struct UASYNC* ua = instance_lite_get_uasync();
if (!uasync_is_running(ua)) { bridge_log(BLEV_ERROR, "create_channel: uasync not running"); return; }
if (!instance_lite_is_running()) { bridge_log(BLEV_ERROR, "create_channel: uasync not running"); return; }
struct chat_create_auto_arg* req = u_calloc(1, sizeof(*req));
if (!req) { bridge_log(BLEV_ERROR, "create_channel: OOM"); return; }
req->inst = instance_lite_get_instance();
snprintf(req->name, sizeof(req->name), "%s", name);
uasync_post(ua, chat_core_create_channel_auto_trampoline, req);
bridge_post_request(chat_core_create_channel_auto_trampoline, req, &req->inst);
}
void utun_bridge_connect_node(const char* address, int port, const char* pubkey_hex) {
@@ -283,14 +281,12 @@ void utun_bridge_request_invite_candidates(const char* channel_id) {
bridge_log(BLEV_ERROR, "request_invite_candidates: null ch_id");
return;
}
struct UASYNC* ua = instance_lite_get_uasync();
if (!uasync_is_running(ua)) { bridge_log(BLEV_ERROR, "request_invite_candidates: uasync not running"); return; }
if (!instance_lite_is_running()) { bridge_log(BLEV_ERROR, "request_invite_candidates: uasync not running"); return; }
struct chat_invite_cand_req* req = u_calloc(1, sizeof(*req));
if (!req) { bridge_log(BLEV_ERROR, "request_invite_candidates: OOM"); return; }
req->inst = instance_lite_get_instance();
snprintf(req->ch_id, sizeof(req->ch_id), "%s", channel_id);
bridge_log(BLEV_INFO, "request_invite_candidates ch=%s", channel_id);
uasync_post(ua, chat_invite_candidates_trampoline, req);
bridge_post_request(chat_invite_candidates_trampoline, req, &req->inst);
}
void utun_bridge_request_invite_link(const char* channel_id, uint64_t target_node_id, uint64_t request_id) {
@@ -298,17 +294,15 @@ void utun_bridge_request_invite_link(const char* channel_id, uint64_t target_nod
bridge_log(BLEV_ERROR, "request_invite_link: null ch_id");
return;
}
struct UASYNC* ua = instance_lite_get_uasync();
if (!uasync_is_running(ua)) { bridge_log(BLEV_ERROR, "request_invite_link: uasync not running"); return; }
if (!instance_lite_is_running()) { bridge_log(BLEV_ERROR, "request_invite_link: uasync not running"); return; }
struct chat_invite_build_req* req = u_calloc(1, sizeof(*req));
if (!req) { bridge_log(BLEV_ERROR, "request_invite_link: OOM"); return; }
req->inst = instance_lite_get_instance();
strncpy(req->ch_id, channel_id, sizeof(req->ch_id) - 1);
req->request_id = request_id;
req->target_node_id = target_node_id;
bridge_log(BLEV_INFO, "request_invite_link ch=%s target=0x%016llx",
channel_id, (unsigned long long)target_node_id);
uasync_post(ua, chat_invite_build_link_trampoline, req);
bridge_post_request(chat_invite_build_link_trampoline, req, &req->inst);
}
void utun_bridge_connect_channel(const char* channel_id) {
@@ -316,14 +310,12 @@ void utun_bridge_connect_channel(const char* channel_id) {
bridge_log(BLEV_ERROR, "connect_channel: null ch_id");
return;
}
struct UASYNC* ua = instance_lite_get_uasync();
if (!uasync_is_running(ua)) return;
if (!instance_lite_is_running()) return;
struct chat_create_auto_arg* req = u_calloc(1, sizeof(*req));
if (!req) return;
req->inst = instance_lite_get_instance();
snprintf(req->name, sizeof(req->name), "%s", channel_id);
bridge_log(BLEV_INFO, "connect_channel ch=%s", channel_id);
uasync_post(ua, chat_core_connect_channel_trampoline, req);
bridge_post_request(chat_core_connect_channel_trampoline, req, &req->inst);
}
void utun_bridge_connect_member_node(const char* channel_id, uint64_t node_id) {
@@ -332,15 +324,13 @@ void utun_bridge_connect_member_node(const char* channel_id, uint64_t node_id) {
channel_id ? channel_id : "(null)", (unsigned long long)node_id);
return;
}
struct UASYNC* ua = instance_lite_get_uasync();
if (!uasync_is_running(ua)) return;
if (!instance_lite_is_running()) return;
struct chat_connect_node_req* req = u_calloc(1, sizeof(*req));
if (!req) { bridge_log(BLEV_ERROR, "connect_member_node: OOM"); return; }
req->inst = instance_lite_get_instance();
strncpy(req->ch_id, channel_id, sizeof(req->ch_id) - 1);
req->node_id = node_id;
bridge_log(BLEV_INFO, "connect_member_node ch=%s node=0x%016llx", channel_id, (unsigned long long)node_id);
uasync_post(ua, chat_core_connect_node_trampoline, req);
bridge_post_request(chat_core_connect_node_trampoline, req, &req->inst);
}
static char* json_escape_alloc(const char* str) {
@@ -712,25 +702,21 @@ int utun_bridge_dm_send(const char* conv_id, const uint8_t* text, size_t length,
}
void utun_bridge_dm_request_conversations(void) {
struct UASYNC* ua = instance_lite_get_uasync();
if (!uasync_is_running(ua)) { bridge_log(BLEV_WARN, "DM conversations: core stopped"); return; }
if (!instance_lite_is_running()) { bridge_log(BLEV_WARN, "DM conversations: core stopped"); return; }
struct dm_conversations_req* req = u_calloc(1, sizeof(*req));
if (!req) { bridge_log(BLEV_ERROR, "DM conversations: OOM"); return; }
req->inst = instance_lite_get_instance();
uasync_post(ua, dm_conversations_trampoline, req);
bridge_post_request(dm_conversations_trampoline, req, &req->inst);
}
void utun_bridge_dm_request_messages(const char* conv_id, uint64_t generation) {
struct UASYNC* ua = instance_lite_get_uasync();
if (!conv_id || !conv_id[0] || !generation || !uasync_is_running(ua)) {
if (!conv_id || !conv_id[0] || !generation || !instance_lite_is_running()) {
bridge_log(BLEV_WARN, "DM history: invalid request/core stopped"); return;
}
struct dm_messages_req* req = u_calloc(1, sizeof(*req));
if (!req) { bridge_log(BLEV_ERROR, "DM history: OOM"); return; }
req->inst = instance_lite_get_instance();
req->generation = generation;
snprintf(req->conv_id, sizeof(req->conv_id), "%s", conv_id);
uasync_post(ua, dm_messages_trampoline, req);
bridge_post_request(dm_messages_trampoline, req, &req->inst);
}
int utun_bridge_dm_send_file(const char* conv_id, const char* path, const char* name, uint64_t generation, uint64_t request_id) {
@@ -1018,19 +1004,15 @@ char* utun_bridge_get_member_detail_json(const char* channel_id, uint64_t node_i
}
int utun_bridge_toggle_member_radio_mute(const char* channel_id, uint64_t node_id) {
struct UTUN_INSTANCE* inst = instance_lite_get_instance();
struct UASYNC* ua = instance_lite_get_uasync();
if (!inst || !uasync_is_running(ua) || !channel_id || !channel_id[0] || strlen(channel_id) >= 64 || !node_id) {
if (!instance_lite_is_running() || !channel_id || !channel_id[0] || strlen(channel_id) >= 64 || !node_id) {
DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "radio: Android mute request rejected: core unavailable or invalid member"); return -1;
}
struct chat_member_radio_mute_req* req = u_calloc(1, sizeof(*req));
struct posted_task* post = u_calloc(1, sizeof(*post));
if (!req || !post) {
DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "radio: Android mute request allocation failed"); u_free(req); u_free(post); return -1;
if (!req) {
DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "radio: Android mute request allocation failed"); u_free(req); return -1;
}
req->inst = inst; req->node_id = node_id; strcpy(req->ch_id, channel_id);
post->callback = chat_core_toggle_member_radio_mute_trampoline; post->arg = req;
uasync_post_reserved(ua, post);
req->node_id = node_id; strcpy(req->ch_id, channel_id);
if (bridge_post_request(chat_core_toggle_member_radio_mute_trampoline, req, &req->inst)) return -1;
return 0;
}
@@ -1099,16 +1081,14 @@ void utun_bridge_set_member_flags(const char* channel_id, uint64_t node_id,
int admin, int supernode, int moder, int storage_flag,
int verified, int deleted) {
if (!channel_id || node_id == 0) { bridge_log(BLEV_ERROR, "set_member_flags: invalid args"); return; }
struct UASYNC* ua = instance_lite_get_uasync();
if (!uasync_is_running(ua)) { bridge_log(BLEV_ERROR, "set_member_flags: uasync not running"); return; }
if (!instance_lite_is_running()) { bridge_log(BLEV_ERROR, "set_member_flags: uasync not running"); return; }
struct member_tags_edit_req* req = u_calloc(1, sizeof(*req));
if (!req) { bridge_log(BLEV_ERROR, "set_member_flags: OOM"); return; }
req->inst = instance_lite_get_instance();
strncpy(req->ch_id, channel_id, sizeof(req->ch_id) - 1);
req->node_id = node_id;
req->admin = admin; req->supernode = supernode; req->moder = moder;
req->storage = storage_flag; req->verified = verified; req->deleted = deleted;
uasync_post(ua, member_tags_edit_trampoline, req);
bridge_post_request(member_tags_edit_trampoline, req, &req->inst);
bridge_log(BLEV_DEBUG, "set_member_flags queued ch=%s nid=0x%016llx admin=%d super=%d moder=%d storage=%d verified=%d deleted=%d",
channel_id, (unsigned long long)node_id, admin, supernode, moder, storage_flag, verified, deleted);
}
@@ -1175,14 +1155,12 @@ char* utun_bridge_get_member_links_json(uint64_t node_id) {
void utun_bridge_transfer_admin(const char* channel_id, uint64_t node_id) {
if (!channel_id || node_id == 0) { bridge_log(BLEV_ERROR, "transfer_admin: invalid args"); return; }
struct UASYNC* ua = instance_lite_get_uasync();
if (!uasync_is_running(ua)) { bridge_log(BLEV_ERROR, "transfer_admin: uasync not running"); return; }
if (!instance_lite_is_running()) { bridge_log(BLEV_ERROR, "transfer_admin: uasync not running"); return; }
struct chat_transfer_admin_req* req = u_calloc(1, sizeof(*req));
if (!req) { bridge_log(BLEV_ERROR, "transfer_admin: OOM"); return; }
req->inst = instance_lite_get_instance();
strncpy(req->ch_id, channel_id, sizeof(req->ch_id) - 1);
req->target_node_id = node_id;
uasync_post(ua, chat_core_transfer_admin_trampoline, req);
bridge_post_request(chat_core_transfer_admin_trampoline, req, &req->inst);
bridge_log(BLEV_INFO, "transfer_admin queued ch=%s → 0x%016llx", channel_id, (unsigned long long)node_id);
}
@@ -1192,13 +1170,11 @@ void utun_bridge_transfer_admin(const char* channel_id, uint64_t node_id) {
void utun_bridge_delete_channel(const char* channel_id) {
if (!channel_id || !channel_id[0]) { bridge_log(BLEV_ERROR, "delete_channel: empty ch_id"); return; }
struct UASYNC* ua = instance_lite_get_uasync();
if (!uasync_is_running(ua)) { bridge_log(BLEV_ERROR, "delete_channel: uasync not running"); return; }
if (!instance_lite_is_running()) { bridge_log(BLEV_ERROR, "delete_channel: uasync not running"); return; }
struct chat_delete_channel_req* req = u_calloc(1, sizeof(*req));
if (!req) { bridge_log(BLEV_ERROR, "delete_channel: OOM"); return; }
req->inst = instance_lite_get_instance();
strncpy(req->ch_id, channel_id, sizeof(req->ch_id) - 1);
uasync_post(ua, chat_core_delete_channel_trampoline, req);
bridge_post_request(chat_core_delete_channel_trampoline, req, &req->inst);
bridge_log(BLEV_INFO, "delete_channel queued ch=%s", channel_id);
}
@@ -1226,14 +1202,12 @@ int utun_bridge_get_sound_on_message(const char* channel_id) {
void utun_bridge_set_sound_on_message(const char* channel_id, int on) {
if (!channel_id || !channel_id[0]) { bridge_log(BLEV_ERROR, "set_sound: empty ch_id"); return; }
struct UASYNC* ua = instance_lite_get_uasync();
if (!uasync_is_running(ua)) { bridge_log(BLEV_ERROR, "set_sound: uasync not running"); return; }
if (!instance_lite_is_running()) { bridge_log(BLEV_ERROR, "set_sound: uasync not running"); return; }
struct sound_setting_req* req = u_calloc(1, sizeof(*req));
if (!req) { bridge_log(BLEV_ERROR, "set_sound: OOM"); return; }
req->inst = instance_lite_get_instance();
strncpy(req->ch_id, channel_id, sizeof(req->ch_id) - 1);
req->on = on ? 1 : 0;
uasync_post(ua, sound_setting_trampoline, req);
bridge_post_request(sound_setting_trampoline, req, &req->inst);
bridge_log(BLEV_INFO, "set_sound queued ch=%s on=%d", channel_id, on ? 1 : 0);
}
@@ -1407,7 +1381,7 @@ void utun_bridge_collect_conn_metrics(uint64_t peer_node_id) {
instance_lite_collect_conn_metrics(peer_node_id);
}
void utun_bridge_restart(const char* config_text) {
int utun_bridge_restart(const char* config_text) {
bridge_log(BLEV_INFO, "bridge restart: posting to instance");
#ifdef __ANDROID__
{
@@ -1415,20 +1389,12 @@ void utun_bridge_restart(const char* config_text) {
instance_lite_set_event_handler(instance_event_handler);
}
#endif
instance_lite_restart(config_text);
/* Wait for instance to be ready before returning to Kotlin.
Normal restart (live): is_running() true immediately → no delay.
Dead restart (after stop): polls until g_running=1 → blocks until init done. */
for (int i = 0; i < 500 && !instance_lite_is_running(); i++)
usleep(10000);
if (!instance_lite_is_running())
bridge_log(BLEV_ERROR, "bridge restart: instance not ready after 5s");
return instance_lite_restart(config_text);
}
void utun_bridge_update_sockets(const char* config_text) {
int utun_bridge_update_sockets(const char* config_text) {
bridge_log(BLEV_INFO, "bridge update_sockets: posting to instance");
instance_lite_update_sockets(config_text);
return instance_lite_update_sockets(config_text);
}
void utun_bridge_ping(void) {
@@ -1496,15 +1462,13 @@ int utun_bridge_attachment_send(const char* channel_id, const char* file_path, c
int utun_bridge_attachment_download(const char* channel_id, int64_t msg_id) {
if (!channel_id || msg_id <= 0) { bridge_log(BLEV_ERROR, "attachment_download: invalid args"); return -1; }
struct UASYNC* ua = instance_lite_get_uasync();
if (!uasync_is_running(ua)) return -1;
if (!instance_lite_is_running()) return -1;
struct attachment_dl_req* req = u_calloc(1, sizeof(*req));
if (!req) return -1;
req->inst = instance_lite_get_instance();
snprintf(req->channel_id, sizeof(req->channel_id), "%s", channel_id);
req->msg_id = msg_id;
bridge_log(BLEV_INFO, "attachment_download: ch=%s msg_id=%lld", channel_id, (long long)msg_id);
uasync_post(ua, chat_core_attachment_download_trampoline, req);
bridge_post_request(chat_core_attachment_download_trampoline, req, &req->inst);
return 0;
}
@@ -1648,37 +1612,27 @@ int utun_bridge_call_start(const char* ch_id, uint64_t peer_node_id, uint64_t ca
(unsigned long long)peer_node_id, (unsigned long long)call_id);
return -1;
}
struct UASYNC* ua = instance_lite_get_uasync();
struct UTUN_INSTANCE* inst = instance_lite_get_instance();
if (!inst || !uasync_is_running(ua)) { bridge_log(BLEV_ERROR, "call_start: core unavailable"); return -1; }
if (!instance_lite_is_running()) { bridge_log(BLEV_ERROR, "call_start: core unavailable"); return -1; }
struct call_start_arg* a = (struct call_start_arg*)u_calloc(1, sizeof(*a));
struct posted_task* post = u_calloc(1, sizeof(*post));
if (!a || !post) { bridge_log(BLEV_ERROR, "call_start: allocation failed"); u_free(a); u_free(post); return -1; }
a->inst = inst;
snprintf(a->ch_id, sizeof(a->ch_id), "%s", ch_id);
if (!a) { bridge_log(BLEV_ERROR, "call_start: allocation failed"); u_free(a); return -1; }
snprintf(a->ch_id, sizeof(a->ch_id), "%s", ch_id);
a->peer_node_id = peer_node_id;
a->call_id = call_id;
bridge_log(BLEV_INFO, "call_start ch=%s peer=0x%llx id=0x%llx",
ch_id, (unsigned long long)peer_node_id, (unsigned long long)call_id);
post->callback = call_start_trampoline; post->arg = a;
uasync_post_reserved(ua, post);
if (bridge_post_request(call_start_trampoline, a, &a->inst)) return -1;
return 0;
}
static int bridge_call_id_action(uint64_t call_id, const char* name,
void (*trampoline)(void*)) {
if (call_id == 0) { bridge_log(BLEV_ERROR, "call_%s: bad call_id=0", name); return -1; }
struct UASYNC* ua = instance_lite_get_uasync();
struct UTUN_INSTANCE* inst = instance_lite_get_instance();
if (!inst || !uasync_is_running(ua)) { bridge_log(BLEV_ERROR, "call_%s: core unavailable", name); return -1; }
if (!instance_lite_is_running()) { bridge_log(BLEV_ERROR, "call_%s: core unavailable", name); return -1; }
struct call_id_arg* a = (struct call_id_arg*)u_calloc(1, sizeof(*a));
struct posted_task* post = u_calloc(1, sizeof(*post));
if (!a || !post) { bridge_log(BLEV_ERROR, "call_%s: allocation failed", name); u_free(a); u_free(post); return -1; }
a->inst = inst;
a->call_id = call_id;
if (!a) { bridge_log(BLEV_ERROR, "call_%s: allocation failed", name); u_free(a); return -1; }
a->call_id = call_id;
bridge_log(BLEV_INFO, "call_%s id=0x%llx", name, (unsigned long long)call_id);
post->callback = trampoline; post->arg = a;
uasync_post_reserved(ua, post);
if (bridge_post_request(trampoline, a, &a->inst)) return -1;
return 0;
}
@@ -1720,15 +1674,12 @@ void utun_bridge_call_audio_set_aec_delay(int frames) {
* ────────────────────────────────────────────────────────────────── */
void utun_bridge_radio_set_active(uint64_t group_id, int on) {
struct UTUN_INSTANCE* inst = instance_lite_get_instance();
struct UASYNC* ua = instance_lite_get_uasync();
if (!inst || !ua) { bridge_log(BLEV_ERROR, "radio_set_active: no instance/ua"); return; }
if (!instance_lite_is_running()) { bridge_log(BLEV_ERROR, "radio_set_active: no instance/ua"); return; }
struct radio_set_active_arg* a = (struct radio_set_active_arg*)u_calloc(1, sizeof(*a));
if (!a) return;
a->inst = inst;
a->group_id = group_id;
a->group_id = group_id;
a->on = on;
uasync_post(ua, radio_set_active_trampoline, a);
bridge_post_request(radio_set_active_trampoline, a, &a->inst);
}
int utun_bridge_radio_subscribers(uint64_t group_id) {
@@ -2473,9 +2424,9 @@ JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeRestart(
if (!configText) return JNI_FALSE;
const char* cfg = (*env)->GetStringUTFChars(env, configText, NULL);
if (!cfg) return JNI_FALSE;
utun_bridge_restart(cfg);
int result = utun_bridge_restart(cfg);
(*env)->ReleaseStringUTFChars(env, configText, cfg);
return JNI_TRUE;
return result == 0 ? JNI_TRUE : JNI_FALSE;
}
JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeUpdateSockets(
@@ -2484,9 +2435,9 @@ JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeUpdateSockets
if (!configText) return JNI_FALSE;
const char* cfg = (*env)->GetStringUTFChars(env, configText, NULL);
if (!cfg) return JNI_FALSE;
utun_bridge_update_sockets(cfg);
int result = utun_bridge_update_sockets(cfg);
(*env)->ReleaseStringUTFChars(env, configText, cfg);
return JNI_TRUE;
return result == 0 ? JNI_TRUE : JNI_FALSE;
}
JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeIsResponsive(
@@ -95,15 +95,14 @@ char* utun_bridge_get_messages_json(const char* channel_id, int limit, const uin
/* Текущий node_id либо 0, если чат не инициализирован. */
uint64_t utun_bridge_get_my_node_id(void);
/* Передать INI в instance_lite_restart, затем до 5с опрашивать is_running.
* У живого ядра старое is_running может сразу дать 1: возврат не гарантирует завершение рестарта.
/* Передать INI в instance_lite_restart: 0 — запрос принят, -1 — отказ с логом. Ожидания готовности здесь нет.
* Событие CHAT_EVT_SERVICE_STARTED подтверждает запуск нового ядра; перед вызовом остановить media workers/DSP. */
void utun_bridge_restart(const char* config_text);
int utun_bridge_restart(const char* config_text);
/* Копирует INI, ставит reload [server:*] в uasync (auto_sockets=android); без готового ядра запрос отбрасывается. */
/* Копирует INI, ставит reload [server:*] в uasync (auto_sockets=android); 0 — принято, -1 — отказ с логом. */
void utun_bridge_update_sockets(const char* config_text);
int utun_bridge_update_sockets(const char* config_text);
/* Локальный ping uasync; is_responsive=1 после обработки последнего ping, 0 до первого/после stop.
* Ожидания и таймаута здесь нет, доступность пиров не проверяется. */