Merge pull request #1550 from davotoula/reduce-errors-ConcurrentModificationException

Reduce errors in log: concurrent modification exception
This commit is contained in:
Vitor Pamplona
2025-11-06 09:50:22 -05:00
committed by GitHub
7 changed files with 51 additions and 29 deletions

View File

@@ -46,7 +46,6 @@ import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transformLatest import kotlinx.coroutines.flow.transformLatest
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlin.collections.map
/** /**
* Maintains several stateflows for each step in processing PeopleLists * Maintains several stateflows for each step in processing PeopleLists

View File

@@ -186,11 +186,11 @@ class AccountFollowsLoaderSubAssembler(
} }
// removes accounts that are not being subscribed anymore. // removes accounts that are not being subscribed anymore.
accountUpdatesJobMap.forEach { // Cancel watchers for accounts no longer observed using a snapshot to avoid CME
if (it.key !in uniqueSubscribedAccounts.keys) { accountUpdatesJobMap.keys
endWatcher(it.key) .toList()
} .filter { it !in uniqueSubscribedAccounts.keys }
} .forEach { endWatcher(it) }
} }
private val accountUpdatesJobMap = mutableMapOf<User, Job>() private val accountUpdatesJobMap = mutableMapOf<User, Job>()

View File

@@ -90,19 +90,30 @@ class UserReportsSubAssembler(
users: Iterable<User>, users: Iterable<User>,
eoseCache: EOSEAccountFast<User>, eoseCache: EOSEAccountFast<User>,
inRelays: Set<NormalizedRelayUrl>, inRelays: Set<NormalizedRelayUrl>,
): Collection<List<User>> = ): Collection<List<User>> {
users if (users.none()) return emptyList()
.groupBy {
eoseCache val relaySnapshot = inRelays.toSet()
.since(it)
?.keys return users
?.intersect(inRelays) .groupBy { user ->
?.hashCode() val relaysForUser = eoseCache.sinceRelaySet(user)
if (relaysForUser.isNullOrEmpty() || relaySnapshot.isEmpty()) {
null
} else {
val intersection = relaysForUser.filter { it in relaySnapshot }.sorted()
if (intersection.isEmpty()) {
null
} else {
intersection.hashCode()
}
}
}.values }.values
.map { .map {
// important to keep in order otherwise the Relay thinks the filter has changed and we REQ again // important to keep in order otherwise the Relay thinks the filter has changed and we REQ again
it.sortedBy { it.pubkeyHex } it.sortedBy { it.pubkeyHex }
} }
}
fun findMinimumEOSEsForUsers( fun findMinimumEOSEsForUsers(
users: List<User>, users: List<User>,

View File

@@ -125,36 +125,51 @@ class EOSEAccountFast<T : Any>(
cacheSize: Int = 20, cacheSize: Int = 20,
) { ) {
private val users: LruCache<T, EOSERelayList> = LruCache<T, EOSERelayList>(cacheSize) private val users: LruCache<T, EOSERelayList> = LruCache<T, EOSERelayList>(cacheSize)
private val lock = Any()
fun addOrUpdate( fun addOrUpdate(
user: T, user: T,
relayUrl: NormalizedRelayUrl, relayUrl: NormalizedRelayUrl,
time: Long, time: Long,
) { ) {
val relayList = users[user] synchronized(lock) {
if (relayList == null) { val relayList = users[user]
val newList = EOSERelayList() if (relayList == null) {
users.put(user, newList) val newList = EOSERelayList()
users.put(user, newList)
newList.addOrUpdate(relayUrl, time) newList.addOrUpdate(relayUrl, time)
} else { } else {
relayList.addOrUpdate(relayUrl, time) relayList.addOrUpdate(relayUrl, time)
}
} }
} }
fun removeEveryoneBut(list: Set<T>) { fun removeEveryoneBut(list: Set<T>) {
users.snapshot().forEach { synchronized(lock) {
if (it.key !in list) { users.snapshot().forEach {
users.remove(it.key) if (it.key !in list) {
users.remove(it.key)
}
} }
} }
} }
fun removeDataFor(user: T) { fun removeDataFor(user: T) {
users.remove(user) synchronized(lock) {
users.remove(user)
}
} }
fun since(key: T) = users[key]?.relayList fun since(key: T): SincePerRelayMap? =
synchronized(lock) {
users[key]?.relayList?.toMutableMap()
}
fun sinceRelaySet(key: T): Set<NormalizedRelayUrl>? =
synchronized(lock) {
users[key]?.relayList?.keys?.toSet()
}
fun newEose( fun newEose(
user: T, user: T,

View File

@@ -34,7 +34,6 @@ import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.observeAccountIsHiddenUser import com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.observeAccountIsHiddenUser
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserIsFollowing import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserIsFollowing
import com.vitorpamplona.amethyst.ui.navigation.navs.EmptyNav.nav
import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.navigation.routes.Route import com.vitorpamplona.amethyst.ui.navigation.routes.Route
import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor

View File

@@ -43,7 +43,6 @@ import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.note.UserComposeNoAction import com.vitorpamplona.amethyst.ui.note.UserComposeNoAction
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.list.PeopleListItem
import com.vitorpamplona.amethyst.ui.theme.DividerThickness import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.FeedPadding import com.vitorpamplona.amethyst.ui.theme.FeedPadding
import com.vitorpamplona.amethyst.ui.theme.HalfHalfHorzModifier import com.vitorpamplona.amethyst.ui.theme.HalfHalfHorzModifier

View File

@@ -28,7 +28,6 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Groups import androidx.compose.material.icons.outlined.Groups
import androidx.compose.material.icons.outlined.Lock import androidx.compose.material.icons.outlined.Lock