From 86fb87e6bbf2959ff964f5f3a99470070b209894 Mon Sep 17 00:00:00 2001 From: Narasimha-sc <166327228+Narasimha-sc@users.noreply.github.com> Date: Mon, 24 Aug 2026 15:32:14 +0000 Subject: [PATCH] android, desktop: fix chat console crash on duplicate list keys Terminal items were keyed by (System.currentTimeMillis(), System.nanoTime()). nanoTime() never goes backwards but is not unique: two items created within one clock tick get identical values, and currentTimeMillis() ties as well, so the whole key collides and LazyColumn throws "Key was already used". Terminal items are created on several threads - sendCmd runs in Dispatchers.IO, received events are processed in another coroutine, and rejected console commands on the main thread - and a command and its response are created back to back, so ties happen, more often with a coarse clock. Key by a per-item sequence number instead. --- .../kotlin/chat/simplex/common/model/SimpleXAPI.kt | 5 ++++- .../kotlin/chat/simplex/common/views/TerminalView.kt | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) 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 54d684160e..9508ae7885 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 @@ -62,6 +62,7 @@ import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.json.* import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicLong import java.time.format.DateTimeFormatter import java.time.format.FormatStyle import java.util.Date @@ -7622,7 +7623,7 @@ abstract class TerminalItem { val date: Instant = Clock.System.now() abstract val label: String abstract val details: String - val createdAtNanos: Long = System.nanoTime() + val itemNo: Long = itemNoGenerator.incrementAndGet() class Cmd(override val id: Long, override val remoteHostId: Long?, val cmd: CC): TerminalItem() { override val label get() = "> ${cmd.cmdString}" @@ -7635,6 +7636,8 @@ abstract class TerminalItem { } companion object { + private val itemNoGenerator = AtomicLong(0) + val sampleData = listOf( Cmd(0, null, CC.ShowActiveUser()), Resp(1, null, API.Result(null, CR.ActiveUser(User.sampleData))) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt index 1bfe88ecf7..0fcd8967e5 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt @@ -165,7 +165,7 @@ fun TerminalLog(floating: Boolean, composeViewHeight: State) { reverseLayout = true, additionalBarOffset = composeViewHeight ) { - items(reversedTerminalItems, key = { item -> item.id to item.createdAtNanos }) { item -> + items(reversedTerminalItems, key = { item -> item.itemNo }) { item -> val clipboard = LocalClipboardManager.current val rhId = item.remoteHostId val rhIdStr = if (rhId == null) "" else "$rhId "