From 495d39969174172501ae1f668a5b7fbfd7ca42e2 Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 29 Oct 2026 01:40:33 +0100 Subject: [PATCH] Block native teardown until viewer and shared mixer cleanup is confirmed --- .../src/main/java/com/utun/chat/data/NativeLib.kt | 1 + .../java/com/utun/chat/data/StreamAudioMixer.kt | 8 ++++++++ .../java/com/utun/chat/data/StreamPlayerEngine.kt | 13 +++++++++++++ 3 files changed, 22 insertions(+) diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/NativeLib.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/NativeLib.kt index 85b6bd33..45c467f9 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/NativeLib.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/NativeLib.kt @@ -74,6 +74,7 @@ object NativeLib { RadioCallController.reset() StreamController.reset(); CallController.reset(); ChatApplication.instance.avatars.stop(); ChatApplication.instance.memberFiles.cancel() CallService.requireReleased() // Не останавливать native при неподтверждённой очистке его media-владельца. + StreamPlayerEngine.stopForCore() check(!CallController.blocksMedia()) { LogManager.addLog("ERROR", "Call", "Core teardown blocked: call media preparation still running") "Call media preparation still running" diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamAudioMixer.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamAudioMixer.kt index 94d50802..d754a817 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamAudioMixer.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamAudioMixer.kt @@ -49,6 +49,14 @@ internal object StreamAudioMixer { LogManager.addLog("INFO", "stream", "shared audio output released for call") } + /** Проверка core teardown без join: живой worker удерживает Android output для retry. */ + fun requireReleased() = synchronized(lock) { + check(inputs.isEmpty() && worker?.isAlive != true) { + LogManager.addLog("ERROR", "stream", "core teardown blocked by mixer inputs=${inputs.size} worker=${worker?.state}") + "Stream mixer cleanup pending" + } + } + private fun run() { var audio: StreamAudioOutput? = null var generated = 0L diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamPlayerEngine.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamPlayerEngine.kt index 951beca4..11761a92 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamPlayerEngine.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/StreamPlayerEngine.kt @@ -24,6 +24,19 @@ class StreamPlayerEngine(private val group: Long, private val source: Long, priv private val mutableViewing = MutableStateFlow(false) val viewing: StateFlow = mutableViewing + /** Main: запросить stop всех viewers; незавершённый cleanup запрещает native stop и допускает повтор. */ + fun stopForCore() { + check(Looper.myLooper() == Looper.getMainLooper()) { "Viewer core teardown requires main" } + players.toList().forEach { it.stop() } + players.removeAll { player -> synchronized(player.releaseLock) { player.released } && player.worker?.isAlive != true } + mutableViewing.value = players.isNotEmpty() + check(players.isEmpty()) { + LogManager.addLog("ERROR", "stream", "core teardown blocked by viewers count=${players.size}; cleanup retained") + "Stream viewers cleanup pending" + } + StreamAudioMixer.requireReleased() + } + /** Закрыть просмотр и дождаться освобождения AudioTrack/decoder вне main thread. */ suspend fun stopForCall() { val stopped = players.toList()