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.transformLatest
import kotlinx.coroutines.flow.update
import kotlin.collections.map
/**
* 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.
accountUpdatesJobMap.forEach {
if (it.key !in uniqueSubscribedAccounts.keys) {
endWatcher(it.key)
}
}
// Cancel watchers for accounts no longer observed using a snapshot to avoid CME
accountUpdatesJobMap.keys
.toList()
.filter { it !in uniqueSubscribedAccounts.keys }
.forEach { endWatcher(it) }
}
private val accountUpdatesJobMap = mutableMapOf<User, Job>()

View File

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

View File

@@ -125,36 +125,51 @@ class EOSEAccountFast<T : Any>(
cacheSize: Int = 20,
) {
private val users: LruCache<T, EOSERelayList> = LruCache<T, EOSERelayList>(cacheSize)
private val lock = Any()
fun addOrUpdate(
user: T,
relayUrl: NormalizedRelayUrl,
time: Long,
) {
val relayList = users[user]
if (relayList == null) {
val newList = EOSERelayList()
users.put(user, newList)
synchronized(lock) {
val relayList = users[user]
if (relayList == null) {
val newList = EOSERelayList()
users.put(user, newList)
newList.addOrUpdate(relayUrl, time)
} else {
relayList.addOrUpdate(relayUrl, time)
newList.addOrUpdate(relayUrl, time)
} else {
relayList.addOrUpdate(relayUrl, time)
}
}
}
fun removeEveryoneBut(list: Set<T>) {
users.snapshot().forEach {
if (it.key !in list) {
users.remove(it.key)
synchronized(lock) {
users.snapshot().forEach {
if (it.key !in list) {
users.remove(it.key)
}
}
}
}
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(
user: T,

View File

@@ -34,7 +34,6 @@ import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.observeAccountIsHiddenUser
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.routes.Route
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.note.UserComposeNoAction
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.FeedPadding
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.fillMaxWidth
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Groups
import androidx.compose.material.icons.outlined.Lock