Preserve event callback lifetime and contain JNI delivery exceptions
This commit is contained in:
1 parent
17f79b16cd
commit
2f4cded218
2 files changed
+58
-24
No files matched your search
@@ -166,8 +166,9 @@ bool utun_bridge_init(const char* db_path, int control_port,
|
||||
|
||||
bridge_log(BLEV_INFO, "bridge init: db=%s port=%d (stub, use nativeStart for real init)",
|
||||
db_path ? db_path : "(null)", control_port);
|
||||
voice_recorder_init(db_path);
|
||||
return true;
|
||||
int result = voice_recorder_init(db_path);
|
||||
if (result) bridge_log(BLEV_ERROR, "bridge init: voice recorder failed rc=%d", result);
|
||||
return result == 0;
|
||||
}
|
||||
|
||||
int utun_bridge_destroy(void) {
|
||||
@@ -1744,6 +1745,7 @@ static pthread_mutex_t g_log_callback_lock = PTHREAD_MUTEX_INITIALIZER; /* За
|
||||
static __thread int g_log_callback_active; /* Callback не может рекурсивно пересылать native-лог того же потока. */
|
||||
static jobject g_event_callback = NULL;
|
||||
static jmethodID g_event_mid = NULL;
|
||||
static pthread_mutex_t g_event_callback_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
enum bridge_query_kind { QUERY_CHANNELS, QUERY_MESSAGES, QUERY_MEMBERS, QUERY_DETAIL, QUERY_LINKS, QUERY_NODE, QUERY_SET_NICK,
|
||||
QUERY_OWNER, QUERY_MEMBERSHIP, QUERY_SETTING, QUERY_SOUND, QUERY_RADIO_SUBSCRIBERS, QUERY_RADIO_START };
|
||||
@@ -1933,30 +1935,55 @@ static jstring jni_text(JNIEnv* env, const char* value) {
|
||||
/* ── JNI event callback (chat_event → Kotlin) ── */
|
||||
|
||||
static void jni_on_event(int type, const uint8_t* data, int len) {
|
||||
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; }
|
||||
if (!g_jvm || len < 0 || (len && !data)) {
|
||||
__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "JNI event: invalid input type=%d bytes=%d", type, len); return;
|
||||
}
|
||||
JNIEnv* env = NULL;
|
||||
int attached = 0;
|
||||
jobject callback = NULL;
|
||||
jbyteArray jdata = NULL;
|
||||
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) {
|
||||
bridge_log(BLEV_ERROR, "[EVT_DIAG] jni_on_event: AttachCurrentThread failed");
|
||||
__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "JNI event: attach failed type=%d", type);
|
||||
return;
|
||||
}
|
||||
attached = 1;
|
||||
} else if (res != JNI_OK) {
|
||||
bridge_log(BLEV_ERROR, "[EVT_DIAG] jni_on_event: GetEnv failed res=%d", (int)res);
|
||||
__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "JNI event: GetEnv failed rc=%d type=%d", res, type);
|
||||
return;
|
||||
}
|
||||
jbyteArray jdata = NULL;
|
||||
if (data && len > 0) {
|
||||
jdata = (*env)->NewByteArray(env, len);
|
||||
if (jdata) (*env)->SetByteArrayRegion(env, jdata, 0, len, (const jbyte*)data);
|
||||
if ((*env)->ExceptionCheck(env)) {
|
||||
__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "JNI event: preserving existing exception type=%d", type); goto finish;
|
||||
}
|
||||
(*env)->CallVoidMethod(env, g_event_callback, g_event_mid, (jint)type, jdata);
|
||||
pthread_mutex_lock(&g_event_callback_lock);
|
||||
jmethodID method = g_event_mid;
|
||||
if (g_event_callback && method) callback = (*env)->NewLocalRef(env, g_event_callback);
|
||||
pthread_mutex_unlock(&g_event_callback_lock);
|
||||
if (!callback) {
|
||||
__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "JNI event: callback unavailable type=%d", type); goto cleanup;
|
||||
}
|
||||
if (len > 0) {
|
||||
jdata = (*env)->NewByteArray(env, len);
|
||||
if (!jdata) {
|
||||
__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "JNI event: payload allocation failed type=%d bytes=%d", type, len); goto cleanup;
|
||||
}
|
||||
(*env)->SetByteArrayRegion(env, jdata, 0, len, (const jbyte*)data);
|
||||
if ((*env)->ExceptionCheck(env)) {
|
||||
__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "JNI event: payload copy failed type=%d bytes=%d", type, len); goto cleanup;
|
||||
}
|
||||
}
|
||||
(*env)->CallVoidMethod(env, callback, method, (jint)type, jdata);
|
||||
if ((*env)->ExceptionCheck(env)) {
|
||||
__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "JNI event: callback threw type=%d bytes=%d", type, len);
|
||||
}
|
||||
cleanup:
|
||||
if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionDescribe(env); (*env)->ExceptionClear(env); }
|
||||
if (jdata) (*env)->DeleteLocalRef(env, jdata);
|
||||
if (attached) (*g_jvm)->DetachCurrentThread(g_jvm);
|
||||
if (callback) (*env)->DeleteLocalRef(env, callback);
|
||||
finish:
|
||||
if (attached && (*g_jvm)->DetachCurrentThread(g_jvm) != JNI_OK)
|
||||
__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "JNI event: detach failed type=%d", type);
|
||||
}
|
||||
|
||||
void instance_event_handler(int type, const uint8_t* data, int len) {
|
||||
@@ -2214,15 +2241,22 @@ JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeSendLocalInvi
|
||||
JNIEXPORT void JNICALL Java_com_utun_chat_data_NativeLib_nativeSetEventCallback(
|
||||
JNIEnv* env, jobject thiz, jobject callback) {
|
||||
(void)thiz;
|
||||
if (g_event_callback) (*env)->DeleteGlobalRef(env, g_event_callback);
|
||||
g_event_callback = callback ? (*env)->NewGlobalRef(env, callback) : NULL;
|
||||
if (g_event_callback) {
|
||||
jclass cls = (*env)->GetObjectClass(env, g_event_callback);
|
||||
g_event_mid = (*env)->GetMethodID(env, cls, "onEvent", "(I[B)V");
|
||||
if (!g_event_mid) bridge_log(BLEV_ERROR, "onEvent method not found on callback");
|
||||
} else {
|
||||
g_event_mid = NULL;
|
||||
jobject next = callback ? (*env)->NewGlobalRef(env, callback) : NULL;
|
||||
if (callback && !next) { bridge_log(BLEV_ERROR, "JNI event: callback allocation failed"); return; }
|
||||
jmethodID method = NULL;
|
||||
if (next) {
|
||||
jclass cls = (*env)->GetObjectClass(env, next);
|
||||
method = cls ? (*env)->GetMethodID(env, cls, "onEvent", "(I[B)V") : NULL;
|
||||
if (cls) (*env)->DeleteLocalRef(env, cls);
|
||||
if (!method) {
|
||||
(*env)->DeleteGlobalRef(env, next); bridge_log(BLEV_ERROR, "JNI event: onEvent method lookup failed"); return;
|
||||
}
|
||||
}
|
||||
pthread_mutex_lock(&g_event_callback_lock);
|
||||
jobject old = g_event_callback; g_event_callback = next; g_event_mid = method;
|
||||
pthread_mutex_unlock(&g_event_callback_lock);
|
||||
if (old) (*env)->DeleteGlobalRef(env, old);
|
||||
bridge_log(BLEV_INFO, "JNI event callback configured=%d", next != NULL);
|
||||
}
|
||||
|
||||
/* ── Pass-through JNI wrappers (unchanged from before) ── */
|
||||
@@ -2240,7 +2274,7 @@ JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeDestroy(
|
||||
g_log_callback = NULL;
|
||||
pthread_mutex_unlock(&g_log_callback_lock);
|
||||
if (previous_log_callback) (*env)->DeleteGlobalRef(env, previous_log_callback);
|
||||
if (g_event_callback) { (*env)->DeleteGlobalRef(env, g_event_callback); g_event_callback = NULL; }
|
||||
Java_com_utun_chat_data_NativeLib_nativeSetEventCallback(env, thiz, NULL);
|
||||
return JNI_TRUE;
|
||||
}
|
||||
|
||||
|
||||
@@ -47,8 +47,8 @@ struct utun_bridge_config {
|
||||
|
||||
/* Сохранить callbacks, установить debug hook, инициализировать voice_recorder(db_path).
|
||||
* Таблицы callbacks копируются, config=NULL отключает прежний config-provider; функции/окружение остаются у caller.
|
||||
* control_port только логируется: listener и ядро здесь не запускаются. Текущая реализация возвращает true
|
||||
* независимо от результата voice_recorder_init; start нужен отдельно. */
|
||||
* control_port только логируется: listener и ядро здесь не запускаются. true — recorder инициализирован,
|
||||
* false — его ошибка с логом; start нужен отдельно. Регистрация callbacks сохраняется для повторного init. */
|
||||
|
||||
bool utun_bridge_init(const char* db_path, int control_port,
|
||||
utun_log_fn on_log,
|
||||
|
||||
Reference in new issue
Block a user