Run Android service shutdown off the main thread and await it before startup
This commit is contained in:
1 parent
d6ea893f16
commit
823bae076f
3 files changed
+26
-16
No files matched your search
@@ -3,6 +3,7 @@ package com.utun.chat.data
|
||||
import com.utun.chat.ChatApplication
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
interface ChatEventCallback {
|
||||
fun onEvent(type: Int, data: ByteArray?)
|
||||
@@ -41,11 +42,11 @@ object NativeLib {
|
||||
return nativeStart(configText)
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
prepareForCoreStop()
|
||||
if (!isReady()) return
|
||||
nativeStop()
|
||||
runBlocking(Dispatchers.Main.immediate) { ChatApplication.instance.memberFiles.coreStopped() }
|
||||
/** Снимает frontend media на main, дожидается worker на IO даже до ready; ошибка запрещает следующий init. */
|
||||
suspend fun stop() {
|
||||
withContext(Dispatchers.Main.immediate) { prepareForCoreStop() }
|
||||
check(withContext(Dispatchers.IO) { nativeStop() }) { "native worker stop failed" }
|
||||
withContext(Dispatchers.Main.immediate) { ChatApplication.instance.memberFiles.coreStopped() }
|
||||
}
|
||||
|
||||
fun setEventCallback(cb: ChatEventCallback) {
|
||||
@@ -54,10 +55,11 @@ object NativeLib {
|
||||
})
|
||||
}
|
||||
|
||||
fun destroy() {
|
||||
prepareForCoreStop()
|
||||
nativeDestroy()
|
||||
runBlocking(Dispatchers.Main.immediate) { ChatApplication.instance.memberFiles.coreStopped() }
|
||||
/** Как stop, дополнительно освобождает JNI global refs только после подтверждённого join. */
|
||||
suspend fun destroy() {
|
||||
withContext(Dispatchers.Main.immediate) { prepareForCoreStop() }
|
||||
check(withContext(Dispatchers.IO) { nativeDestroy() }) { "native worker destroy failed" }
|
||||
withContext(Dispatchers.Main.immediate) { ChatApplication.instance.memberFiles.coreStopped() }
|
||||
}
|
||||
|
||||
fun restart(dbPath: String, controlPort: Int, configText: String): Boolean {
|
||||
@@ -347,10 +349,10 @@ object NativeLib {
|
||||
|
||||
private external fun nativeInit(dbPath: String, controlPort: Int, config: Any, logCb: Any): Boolean
|
||||
private external fun nativeStart(configText: String): Boolean
|
||||
private external fun nativeStop()
|
||||
private external fun nativeStop(): Boolean
|
||||
private external fun nativeRequestCpuLoad(): Boolean
|
||||
private external fun nativeSetEventCallback(cb: Any)
|
||||
private external fun nativeDestroy()
|
||||
private external fun nativeDestroy(): Boolean
|
||||
private external fun nativeSendMessage(channelId: String, text: ByteArray, replyTo: ByteArray, requestId: Long): Boolean
|
||||
private external fun nativeEditMessage(channelId: String, messageId: ByteArray, version: Long, text: ByteArray, requestId: Long): Boolean
|
||||
private external fun nativeCreateChannel(name: String, channelId: String)
|
||||
|
||||
@@ -33,10 +33,15 @@ class HeadlessService : Service() {
|
||||
}, onFailure = ::stopSelf, log = { level, message -> LogManager.addLog(level, "SRV", message) })
|
||||
companion object {
|
||||
private var currentService: HeadlessService? = null
|
||||
// Живёт дольше экземпляра Service: новый init не пересекается с очисткой предыдущего worker.
|
||||
private val shutdownScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
|
||||
private val shutdown = CoreShutdown(shutdownScope, NativeLib::stop,
|
||||
log = { level, message -> LogManager.addLog(level, "SRV", message) })
|
||||
|
||||
/** Main thread: дождаться готового ядра текущего Service; ошибка/остановка прерывает вызывающую операцию.
|
||||
* Без Service ожидания нет: настройки сохраняются для следующего запуска. */
|
||||
suspend fun awaitStartup() {
|
||||
shutdown.await()
|
||||
val service = currentService ?: return
|
||||
service.startup.awaitReady()
|
||||
check(currentService === service && NativeLib.isReady()) { "service core stopped while awaiting startup" }
|
||||
@@ -64,6 +69,7 @@ class HeadlessService : Service() {
|
||||
startForeground(1, notification())
|
||||
|
||||
startup.start(prepare = {
|
||||
shutdown.await()
|
||||
val provider = ChatApplication.instance.configProvider
|
||||
val (dbDir, listenPort) = withContext(Dispatchers.IO) {
|
||||
(provider.getString("db.path") ?: filesDir.absolutePath) to provider.getInt("node.listen_port")
|
||||
@@ -82,7 +88,7 @@ class HeadlessService : Service() {
|
||||
LogManager.addLog("INFO", "SRV", "uTun service stopping")
|
||||
NetworkChangeMonitor.stop()
|
||||
NetworkActivityMonitor.stop()
|
||||
NativeLib.stop()
|
||||
shutdown.request()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
|
||||
@@ -1889,11 +1889,12 @@ JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeStart(
|
||||
|
||||
/* ── nativeStop() ── */
|
||||
|
||||
JNIEXPORT void JNICALL Java_com_utun_chat_data_NativeLib_nativeStop(
|
||||
JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeStop(
|
||||
JNIEnv* env, jobject thiz) {
|
||||
(void)env; (void)thiz;
|
||||
utun_bridge_destroy();
|
||||
if (utun_bridge_destroy()) return JNI_FALSE;
|
||||
udp_log_stop();
|
||||
return JNI_TRUE;
|
||||
}
|
||||
|
||||
/* ── nativeInit (legacy, just saves callbacks) ── */
|
||||
@@ -2067,14 +2068,15 @@ JNIEXPORT void JNICALL Java_com_utun_chat_data_NativeLib_nativeSetEventCallback(
|
||||
|
||||
/* ── Pass-through JNI wrappers (unchanged from before) ── */
|
||||
|
||||
JNIEXPORT void JNICALL Java_com_utun_chat_data_NativeLib_nativeDestroy(
|
||||
JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeDestroy(
|
||||
JNIEnv* env, jobject thiz) {
|
||||
(void)thiz;
|
||||
utun_bridge_destroy();
|
||||
if (utun_bridge_destroy()) return JNI_FALSE;
|
||||
udp_log_stop();
|
||||
if (g_config_callback) { (*env)->DeleteGlobalRef(env, g_config_callback); g_config_callback = NULL; }
|
||||
if (g_log_callback) { (*env)->DeleteGlobalRef(env, g_log_callback); g_log_callback = NULL; }
|
||||
if (g_event_callback) { (*env)->DeleteGlobalRef(env, g_event_callback); g_event_callback = NULL; }
|
||||
return JNI_TRUE;
|
||||
}
|
||||
|
||||
/* Пустой массив означает отсутствие ссылки; иначе принимаем только полный глобальный ID. */
|
||||
|
||||
Reference in new issue
Block a user