From df87557ef3720fef6a01a14340947f0b188da16b Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 19 May 2026 17:57:15 +0000 Subject: [PATCH] fix: stable sort in ShortsFeedFilter to avoid TimSort contract crash A newer AddressableEvent (e.g. VideoVerticalEvent) arriving on a relay thread can swap a Note's event mid-sort, changing the createdAt value the comparator returned moments earlier. TimSort then trips "Comparison method violates its general contract!" and the feed refresh crashes. Snapshot createdAt once per note before sorting via a new sortedByDefaultFeedOrder() helper. --- .../vitorpamplona/amethyst/ui/dal/DefaultFeedOrder.kt | 9 +++++++++ .../ui/screen/loggedIn/shorts/dal/ShortsFeedFilter.kt | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/dal/DefaultFeedOrder.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/dal/DefaultFeedOrder.kt index aa85be4546..ebab80962a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/dal/DefaultFeedOrder.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/dal/DefaultFeedOrder.kt @@ -32,3 +32,12 @@ val DefaultFeedOrderEvent: Comparator = val DefaultFeedOrderCard: Comparator = compareByDescending { it.createdAt() }.thenBy { it.id() } + +// Snapshots createdAt once per note so the comparator stays consistent even if +// another thread swaps a Note's event mid-sort (e.g. a newer AddressableEvent +// arriving from a relay). Avoids TimSort's "Comparison method violates its +// general contract!" IllegalArgumentException. +fun Iterable.sortedByDefaultFeedOrder(): List = + map { it to (it.createdAt() ?: 0L) } + .sortedWith(compareByDescending> { it.second }.thenBy { it.first.idHex }) + .map { it.first } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/shorts/dal/ShortsFeedFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/shorts/dal/ShortsFeedFilter.kt index 5d9765ff12..1a23ea7c14 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/shorts/dal/ShortsFeedFilter.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/shorts/dal/ShortsFeedFilter.kt @@ -28,8 +28,8 @@ import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.TopFilter import com.vitorpamplona.amethyst.model.filterIntoSet import com.vitorpamplona.amethyst.ui.dal.AdditiveFeedFilter -import com.vitorpamplona.amethyst.ui.dal.DefaultFeedOrder import com.vitorpamplona.amethyst.ui.dal.FilterByListParams +import com.vitorpamplona.amethyst.ui.dal.sortedByDefaultFeedOrder import com.vitorpamplona.amethyst.ui.screen.loggedIn.video.dal.SupportedContent import com.vitorpamplona.amethyst.ui.screen.loggedIn.video.datasource.SUPPORTED_VIDEO_FEED_MIME_TYPES_SET import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent @@ -113,5 +113,5 @@ class ShortsFeedFilter( (params.isHiddenList || account.isAcceptable(note)) } - override fun sort(items: Set): List = items.sortedWith(DefaultFeedOrder) + override fun sort(items: Set): List = items.sortedByDefaultFeedOrder() }