From 38eb51c679ef4c0b61d57d70561bdcc335bd1d07 Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 6 Aug 2026 00:06:28 +0300 Subject: [PATCH] =?UTF-8?q?fix:=20Android=20event=20handler=20lost=20after?= =?UTF-8?q?=20restart=20=E2=80=94=20g=5Fevent=5Fhandler=20now=20preserved?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause: instance_lite_restart was creating new thread without re-setting g_event_handler, causing all chat events (CHANNEL_UPDATED, CONNECT_RESULT, etc.) to be silently dropped after app restart. Fixes: - instance_lite_restart: save/restore g_event_handler across hard restart - instance_thread restart loop: re-call chat_event_set_handler after uasync_create - utun_bridge_restart: call instance_lite_set_event_handler before restart - AppEventHandler SharedFlow replay: 1 → 16 to not lose early init events - ViewModel: added diagnostic logging for channel/connect events - Keep diagnostic logs in instance_lite (set_handler, chat_event_forward NULL check) - instance_event_handler: removed static for cross-block forward declaration --- src/chat/chat_event.c | 8 ++++++++ .../java/com/utun/chat/ChatApplication.kt | 2 +- .../com/utun/chat/viewmodel/ChatViewModel.kt | 5 +++-- .../jni_bridge/android_jni_bridge.c | 20 +++++++++++++++---- .../libutun_lite/instance_lite.c | 11 ++++++++-- 5 files changed, 37 insertions(+), 9 deletions(-) diff --git a/src/chat/chat_event.c b/src/chat/chat_event.c index e2d963aa..bd3f3cb2 100644 --- a/src/chat/chat_event.c +++ b/src/chat/chat_event.c @@ -8,9 +8,17 @@ #include "chat_event.h" #include "../lib/debug_config.h" +#ifdef __ANDROID__ +#include +#endif + static chat_event_handler_fn g_handler = NULL; void chat_event_set_handler(chat_event_handler_fn handler) { +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_INFO, "utun-evt", "chat_event_set_handler: handler=%p old=%p", + (void*)handler, (void*)g_handler); +#endif g_handler = handler; } diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/ChatApplication.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/ChatApplication.kt index 8a039f93..4bd6da21 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/ChatApplication.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/ChatApplication.kt @@ -10,7 +10,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow object AppEventHandler : ChatEventCallback { - private val _events = MutableSharedFlow>(replay = 1, extraBufferCapacity = 64) + private val _events = MutableSharedFlow>(replay = 16, extraBufferCapacity = 64) val events: SharedFlow> = _events override fun onEvent(type: Int, data: ByteArray?) { diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/viewmodel/ChatViewModel.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/viewmodel/ChatViewModel.kt index 8c8f45e9..e2fd22e2 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/viewmodel/ChatViewModel.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/viewmodel/ChatViewModel.kt @@ -181,7 +181,7 @@ class ChatViewModel : ViewModel() { } } 2 -> { /* CONNECT_RESULT: [node_id:8][result:4][channel_id:8] */ - if (repo == null || data == null || data.size < 20) return + if (repo == null || data == null || data.size < 20) { LogManager.addLog("WARN", "VM", "CONNECT_RESULT skipped repo=${repo != null} data=${data != null}"); return } val buf = java.nio.ByteBuffer.wrap(data).order(java.nio.ByteOrder.LITTLE_ENDIAN) val nodeId = buf.getLong() val result = buf.getInt() @@ -189,9 +189,10 @@ class ChatViewModel : ViewModel() { LogManager.addLog( if (result == 0) "INFO" else "ERROR", "JOIN", "connect to 0x%016x ch=$chId result=$result".format(nodeId)) + LogManager.addLog("INFO", "VM", "CONNECT_RESULT result=$result — refreshChannels") if (result == 0) refreshChannels() } - 4 -> if (repo != null) refreshChannels() /* CHANNEL_UPDATED */ + 4 -> { if (repo != null) { LogManager.addLog("INFO", "VM", "CHANNEL_UPDATED — refreshChannels"); refreshChannels() } else { LogManager.addLog("WARN", "VM", "CHANNEL_UPDATED skipped — repo is NULL") } } 12 -> LogManager.addLog("INFO", "VM", "service started") 13 -> LogManager.addLog("INFO", "VM", "service stopped") 16 -> { /* NODEINFO_UPDATED: [node_id:8][presence:1][up:1][best_rtt:2] */ diff --git a/tools/chatgui-android/jni_bridge/android_jni_bridge.c b/tools/chatgui-android/jni_bridge/android_jni_bridge.c index 5ec2a577..509dab17 100644 --- a/tools/chatgui-android/jni_bridge/android_jni_bridge.c +++ b/tools/chatgui-android/jni_bridge/android_jni_bridge.c @@ -1021,6 +1021,12 @@ char* utun_bridge_get_member_links_json(uint64_t node_id) { void utun_bridge_restart(const char* config_text) { bridge_log(BLEV_INFO, "bridge restart: posting to instance"); +#ifdef __ANDROID__ + { + void instance_event_handler(int type, const uint8_t* data, int len); + instance_lite_set_event_handler(instance_event_handler); + } +#endif instance_lite_restart(config_text); } @@ -1235,14 +1241,20 @@ static void jni_on_log(int level, const char* category, const char* message) { /* ── JNI event callback (chat_event → Kotlin) ── */ static void jni_on_event(int type, const uint8_t* data, int len) { - if (!g_jvm || !g_event_callback || !g_event_mid) return; + if (!g_jvm) { bridge_log(BLEV_ERROR, "[EVT_DIAG] jni_on_event: g_jvm=NULL"); return; } + if (!g_event_callback) { bridge_log(BLEV_ERROR, "[EVT_DIAG] jni_on_event: g_event_callback=NULL"); return; } + if (!g_event_mid) { bridge_log(BLEV_ERROR, "[EVT_DIAG] jni_on_event: g_event_mid=NULL"); return; } JNIEnv* env = NULL; int attached = 0; jint res = (*g_jvm)->GetEnv(g_jvm, (void**)&env, JNI_VERSION_1_6); if (res == JNI_EDETACHED) { - if ((*g_jvm)->AttachCurrentThread(g_jvm, &env, NULL) != JNI_OK) return; + if ((*g_jvm)->AttachCurrentThread(g_jvm, &env, NULL) != JNI_OK) { + bridge_log(BLEV_ERROR, "[EVT_DIAG] jni_on_event: AttachCurrentThread failed"); + return; + } attached = 1; } else if (res != JNI_OK) { + bridge_log(BLEV_ERROR, "[EVT_DIAG] jni_on_event: GetEnv failed res=%d", (int)res); return; } jbyteArray jdata = NULL; @@ -1255,8 +1267,8 @@ static void jni_on_event(int type, const uint8_t* data, int len) { if (attached) (*g_jvm)->DetachCurrentThread(g_jvm); } -static void instance_event_handler(int type, const uint8_t* data, int len) { - bridge_log(BLEV_DEBUG, "event type=%d len=%d", type, len); +void instance_event_handler(int type, const uint8_t* data, int len) { + bridge_log(BLEV_DEBUG, "[EVT_DIAG] event type=%d len=%d", type, len); jni_on_event(type, data, len); } diff --git a/tools/chatgui-android/libutun_lite/instance_lite.c b/tools/chatgui-android/libutun_lite/instance_lite.c index 37eff30d..49c7c854 100644 --- a/tools/chatgui-android/libutun_lite/instance_lite.c +++ b/tools/chatgui-android/libutun_lite/instance_lite.c @@ -137,7 +137,8 @@ static void cfg_get_val(const char* text, const char* key, char* out, size_t out /* ── Chat event forward → Kotlin callback ── */ static void chat_event_forward(int type, const uint8_t* data, int len) { - if (g_event_handler) g_event_handler(type, data, len); + if (g_event_handler) { g_event_handler(type, data, len); return; } + IL_LOGE("[EVT_DIAG] chat_event_forward: g_event_handler is NULL! type=%d len=%d", type, len); } static void nodeinfo_event_cb(struct TOPO_GROUP* group, struct TOPO_GROUP_NODE* nq, void* arg) { @@ -214,6 +215,7 @@ static void* instance_thread(void* arg) { uasync_set_timeout(g_ua, 2000, NULL, udp_trampoline, "udp_log_flush"); } + IL_LOGI("[EVT_DIAG] thread: about to set chat_event handler, g_event_handler=%p", (void*)g_event_handler); chat_event_set_handler(chat_event_forward); ensure_keys(config); @@ -301,6 +303,8 @@ static void* instance_thread(void* arg) { g_ua = uasync_create(); if (!g_ua) { IL_LOGE("poll exit: uasync_create failed"); break; } + chat_event_set_handler(chat_event_forward); + char* cfg = g_restart_config; g_restart_config = NULL; g_do_restart = 0; @@ -407,9 +411,11 @@ void instance_lite_stop(void) { void instance_lite_restart(const char* new_config_text) { if (!new_config_text) return; + instance_lite_event_fn saved_handler = g_event_handler; if (!g_ua || !g_inst || !g_running) { - IL_LOGI("restart: instance dead, doing hard restart via stop+start"); + IL_LOGI("restart: instance dead, doing hard restart via stop+start (saved_handler=%p)", (void*)saved_handler); if (g_inst) instance_lite_stop(); + g_event_handler = saved_handler; instance_lite_start(new_config_text); return; } @@ -477,6 +483,7 @@ void instance_lite_regenerate_keys(void) { } void instance_lite_set_event_handler(instance_lite_event_fn handler) { + IL_LOGI("[EVT_DIAG] instance_lite_set_event_handler handler=%p g_running=%d", (void*)handler, g_running); g_event_handler = handler; if (g_running) chat_event_set_handler(chat_event_forward); }