Browse Source

fix: Android event handler lost after restart — g_event_handler now preserved

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
topo_upd
evgeny 2 months ago
parent
commit
38eb51c679
  1. 8
      src/chat/chat_event.c
  2. 2
      tools/chatgui-android/app/src/main/java/com/utun/chat/ChatApplication.kt
  3. 5
      tools/chatgui-android/app/src/main/java/com/utun/chat/viewmodel/ChatViewModel.kt
  4. 20
      tools/chatgui-android/jni_bridge/android_jni_bridge.c
  5. 11
      tools/chatgui-android/libutun_lite/instance_lite.c

8
src/chat/chat_event.c

@ -8,9 +8,17 @@
#include "chat_event.h"
#include "../lib/debug_config.h"
#ifdef __ANDROID__
#include <android/log.h>
#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;
}

2
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<Pair<Int, ByteArray?>>(replay = 1, extraBufferCapacity = 64)
private val _events = MutableSharedFlow<Pair<Int, ByteArray?>>(replay = 16, extraBufferCapacity = 64)
val events: SharedFlow<Pair<Int, ByteArray?>> = _events
override fun onEvent(type: Int, data: ByteArray?) {

5
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] */

20
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);
}

11
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);
}

Loading…
Cancel
Save