From 5ea5e43c23036c4c9ff280950153e3c450cbaddb Mon Sep 17 00:00:00 2001 From: denis0001-dev Date: Fri, 13 Feb 2026 21:50:51 +0300 Subject: [PATCH] Fix crash Signed-off-by: denis0001-dev --- .../kotlin/ru/fromchat/ui/chat/ChatPanel.kt | 30 +++++++++++-------- .../ru/fromchat/ui/chat/PublicChatPanel.kt | 4 +-- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/app/shared/src/commonMain/kotlin/ru/fromchat/ui/chat/ChatPanel.kt b/app/shared/src/commonMain/kotlin/ru/fromchat/ui/chat/ChatPanel.kt index 6670164..16574bd 100644 --- a/app/shared/src/commonMain/kotlin/ru/fromchat/ui/chat/ChatPanel.kt +++ b/app/shared/src/commonMain/kotlin/ru/fromchat/ui/chat/ChatPanel.kt @@ -5,6 +5,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import kotlinx.serialization.Serializable import ru.fromchat.api.Message import ru.fromchat.api.WebSocketMessage @@ -79,21 +81,25 @@ abstract class ChatPanel( } } + private val addMessageMutex = Mutex() + /** - * Add message to list + * Add message to list. Mutex prevents duplicate adds when same update + * is processed concurrently from multiple WebSocket connections. */ - protected fun addMessage(message: Message) { - val messageExists = _state.messages.any { it.id == message.id } - if (!messageExists) { - Logger.d("ChatPanel", "Adding message: id=${message.id}, content=${message.content.take(50)}") - // Add message and sort by timestamp (ISO 8601 strings sort correctly lexicographically) - updateState { currentState -> - val newMessages = (currentState.messages + message).sortedBy { it.timestamp } - Logger.d("ChatPanel", "Messages count after add: ${newMessages.size}") - currentState.copy(messages = newMessages) + protected suspend fun addMessage(message: Message) { + addMessageMutex.withLock { + val messageExists = _state.messages.any { it.id == message.id } + if (!messageExists) { + Logger.d("ChatPanel", "Adding message: id=${message.id}, content=${message.content.take(50)}") + updateState { currentState -> + val newMessages = (currentState.messages + message).sortedBy { it.timestamp } + Logger.d("ChatPanel", "Messages count after add: ${newMessages.size}") + currentState.copy(messages = newMessages) + } + } else { + Logger.d("ChatPanel", "Message already exists: id=${message.id}") } - } else { - Logger.d("ChatPanel", "Message already exists: id=${message.id}") } } diff --git a/app/shared/src/commonMain/kotlin/ru/fromchat/ui/chat/PublicChatPanel.kt b/app/shared/src/commonMain/kotlin/ru/fromchat/ui/chat/PublicChatPanel.kt index e79fac6..b860507 100644 --- a/app/shared/src/commonMain/kotlin/ru/fromchat/ui/chat/PublicChatPanel.kt +++ b/app/shared/src/commonMain/kotlin/ru/fromchat/ui/chat/PublicChatPanel.kt @@ -91,7 +91,7 @@ class PublicChatPanel( } } - private fun handleSingleUpdate(updateMessage: WebSocketMessage) { + private suspend fun handleSingleUpdate(updateMessage: WebSocketMessage) { val json = ApiClient.json when (updateMessage.type) { "newMessage" -> { @@ -154,7 +154,7 @@ class PublicChatPanel( val data = message.data ?: return val updatesData = json.decodeFromJsonElement(WebSocketUpdatesData.serializer(), data) Logger.d("PublicChatPanel", "Received ${updatesData.updates.size} batched updates (seq: ${updatesData.seq})") - updatesData.updates.forEach { update -> + for (update in updatesData.updates) { handleSingleUpdate(update) } } else {