From 8ffe1c23c176b7d857b3ef534be26e2b5a67b41d Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Thu, 12 Oct 2023 20:19:57 +0800 Subject: [PATCH] android, desktop: better handling of parallel updates of chats (#3204) * android, desktop: better handling of parallel updates of chats * one more case --- .../main/java/chat/simplex/app/SimplexApp.kt | 32 +++++++++-------- .../chat/simplex/common/model/ChatModel.kt | 34 +++++++++++-------- .../chat/simplex/common/model/SimpleXAPI.kt | 14 +++++--- .../common/views/database/DatabaseView.kt | 8 +++-- 4 files changed, 53 insertions(+), 35 deletions(-) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt index f70032788..d2c446517 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt @@ -9,12 +9,14 @@ import chat.simplex.common.helpers.APPLICATION_ID import chat.simplex.common.helpers.requiresIgnoringBattery import chat.simplex.common.model.* import chat.simplex.common.model.ChatController.appPrefs +import chat.simplex.common.model.ChatModel.updatingChatsMutex import chat.simplex.common.views.helpers.* import chat.simplex.common.views.onboarding.OnboardingStage import chat.simplex.common.platform.* import chat.simplex.common.views.call.RcvCallInvitation import com.jakewharton.processphoenix.ProcessPhoenix import kotlinx.coroutines.* +import kotlinx.coroutines.sync.withLock import java.io.* import java.util.* import java.util.concurrent.TimeUnit @@ -52,21 +54,23 @@ class SimplexApp: Application(), LifecycleEventObserver { Lifecycle.Event.ON_START -> { isAppOnForeground = true if (chatModel.chatRunning.value == true) { - kotlin.runCatching { - val currentUserId = chatModel.currentUser.value?.userId - val chats = ArrayList(chatController.apiGetChats()) - /** Active user can be changed in background while [ChatController.apiGetChats] is executing */ - if (chatModel.currentUser.value?.userId == currentUserId) { - val currentChatId = chatModel.chatId.value - val oldStats = if (currentChatId != null) chatModel.getChat(currentChatId)?.chatStats else null - if (oldStats != null) { - val indexOfCurrentChat = chats.indexOfFirst { it.id == currentChatId } - /** Pass old chatStats because unreadCounter can be changed already while [ChatController.apiGetChats] is executing */ - if (indexOfCurrentChat >= 0) chats[indexOfCurrentChat] = chats[indexOfCurrentChat].copy(chatStats = oldStats) + updatingChatsMutex.withLock { + kotlin.runCatching { + val currentUserId = chatModel.currentUser.value?.userId + val chats = ArrayList(chatController.apiGetChats()) + /** Active user can be changed in background while [ChatController.apiGetChats] is executing */ + if (chatModel.currentUser.value?.userId == currentUserId) { + val currentChatId = chatModel.chatId.value + val oldStats = if (currentChatId != null) chatModel.getChat(currentChatId)?.chatStats else null + if (oldStats != null) { + val indexOfCurrentChat = chats.indexOfFirst { it.id == currentChatId } + /** Pass old chatStats because unreadCounter can be changed already while [ChatController.apiGetChats] is executing */ + if (indexOfCurrentChat >= 0) chats[indexOfCurrentChat] = chats[indexOfCurrentChat].copy(chatStats = oldStats) + } + chatModel.updateChats(chats) } - chatModel.updateChats(chats) - } - }.onFailure { Log.e(TAG, it.stackTraceToString()) } + }.onFailure { Log.e(TAG, it.stackTraceToString()) } + } } } Lifecycle.Event.ON_RESUME -> { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index 88ad78612..ac5f9fb8a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -6,7 +6,6 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.font.* import androidx.compose.ui.text.style.TextDecoration -import chat.simplex.common.model.* import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.call.* @@ -16,6 +15,8 @@ import chat.simplex.res.MR import dev.icerock.moko.resources.ImageResource import dev.icerock.moko.resources.StringResource import kotlinx.coroutines.* +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import kotlinx.datetime.* import kotlinx.datetime.TimeZone import kotlinx.serialization.* @@ -102,6 +103,8 @@ object ChatModel { val filesToDelete = mutableSetOf() val simplexLinkMode by lazy { mutableStateOf(ChatController.appPrefs.simplexLinkMode.get()) } + var updatingChatsMutex: Mutex = Mutex() + fun getUser(userId: Long): User? = if (currentUser.value?.userId == userId) { currentUser.value } else { @@ -198,7 +201,7 @@ object ChatModel { } } - suspend fun addChatItem(cInfo: ChatInfo, cItem: ChatItem) { + suspend fun addChatItem(cInfo: ChatInfo, cItem: ChatItem) = updatingChatsMutex.withLock { // update previews val i = getChatIndex(cInfo.id) val chat: Chat @@ -221,10 +224,11 @@ object ChatModel { } else { addChat(Chat(chatInfo = cInfo, chatItems = arrayListOf(cItem))) } - // add to current chat - if (chatId.value == cInfo.id) { - Log.d(TAG, "TODOCHAT: addChatItem: adding to chat ${chatId.value} from ${cInfo.id} ${cItem.id}, size ${chatItems.size}") - withContext(Dispatchers.Main) { + Log.d(TAG, "TODOCHAT: addChatItem: adding to chat ${chatId.value} from ${cInfo.id} ${cItem.id}, size ${chatItems.size}") + withContext(Dispatchers.Main) { + // add to current chat + if (chatId.value == cInfo.id) { + Log.d(TAG, "TODOCHAT: addChatItem: chatIds are equal, size ${chatItems.size}") // Prevent situation when chat item already in the list received from backend if (chatItems.none { it.id == cItem.id }) { if (chatItems.lastOrNull()?.id == ChatItem.TEMP_LIVE_CHAT_ITEM_ID) { @@ -238,7 +242,7 @@ object ChatModel { } } - suspend fun upsertChatItem(cInfo: ChatInfo, cItem: ChatItem): Boolean { + suspend fun upsertChatItem(cInfo: ChatInfo, cItem: ChatItem): Boolean = updatingChatsMutex.withLock { // update previews val i = getChatIndex(cInfo.id) val chat: Chat @@ -258,10 +262,10 @@ object ChatModel { addChat(Chat(chatInfo = cInfo, chatItems = arrayListOf(cItem))) res = true } - // update current chat - return if (chatId.value == cInfo.id) { - Log.d(TAG, "TODOCHAT: upsertChatItem: upserting to chat ${chatId.value} from ${cInfo.id} ${cItem.id}, size ${chatItems.size}") - withContext(Dispatchers.Main) { + Log.d(TAG, "TODOCHAT: upsertChatItem: upserting to chat ${chatId.value} from ${cInfo.id} ${cItem.id}, size ${chatItems.size}") + return withContext(Dispatchers.Main) { + // update current chat + if (chatId.value == cInfo.id) { val itemIndex = chatItems.indexOfFirst { it.id == cItem.id } if (itemIndex >= 0) { chatItems[itemIndex] = cItem @@ -272,15 +276,15 @@ object ChatModel { Log.d(TAG, "TODOCHAT: upsertChatItem: added to chat $chatId from ${cInfo.id} ${cItem.id}, size ${chatItems.size}") true } + } else { + res } - } else { - res } } suspend fun updateChatItem(cInfo: ChatInfo, cItem: ChatItem) { - if (chatId.value == cInfo.id) { - withContext(Dispatchers.Main) { + withContext(Dispatchers.Main) { + if (chatId.value == cInfo.id) { val itemIndex = chatItems.indexOfFirst { it.id == cItem.id } if (itemIndex >= 0) { chatItems[itemIndex] = cItem diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 3644268ba..dc2c3c0f2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -4,6 +4,7 @@ import chat.simplex.common.views.helpers.* import androidx.compose.runtime.* import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter +import chat.simplex.common.model.ChatModel.updatingChatsMutex import dev.icerock.moko.resources.compose.painterResource import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* @@ -16,6 +17,7 @@ import com.charleskorn.kaml.YamlConfiguration import chat.simplex.res.MR import com.russhwolf.settings.Settings import kotlinx.coroutines.* +import kotlinx.coroutines.sync.withLock import kotlinx.datetime.Clock import kotlinx.datetime.Instant import kotlinx.serialization.* @@ -349,8 +351,10 @@ object ChatController { startReceiver() Log.d(TAG, "startChat: started") } else { - val chats = apiGetChats() - chatModel.updateChats(chats) + updatingChatsMutex.withLock { + val chats = apiGetChats() + chatModel.updateChats(chats) + } Log.d(TAG, "startChat: running") } } catch (e: Error) { @@ -384,8 +388,10 @@ object ChatController { suspend fun getUserChatData() { chatModel.userAddress.value = apiGetUserAddress() chatModel.chatItemTTL.value = getChatItemTTL() - val chats = apiGetChats() - chatModel.updateChats(chats) + updatingChatsMutex.withLock { + val chats = apiGetChats() + chatModel.updateChats(chats) + } } private fun startReceiver() { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt index fa0f8f54d..0cca35474 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt @@ -20,11 +20,13 @@ import androidx.compose.ui.text.* import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import chat.simplex.common.model.* +import chat.simplex.common.model.ChatModel.updatingChatsMutex import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* import chat.simplex.common.views.usersettings.* import chat.simplex.common.platform.* import chat.simplex.res.MR +import kotlinx.coroutines.sync.withLock import kotlinx.datetime.* import java.io.* import java.net.URI @@ -620,8 +622,10 @@ private fun afterSetCiTTL( appFilesCountAndSize.value = directoryFileCountAndSize(appFilesDir.absolutePath) withApi { try { - val chats = m.controller.apiGetChats() - m.updateChats(chats) + updatingChatsMutex.withLock { + val chats = m.controller.apiGetChats() + m.updateChats(chats) + } } catch (e: Exception) { Log.e(TAG, "apiGetChats error: ${e.message}") }