mirror of
https://github.com/greenart7c3/Amber.git
synced 2026-10-05 19:08:23 +00:00
Merge #6e30bc49: fix(profile-subscriptions): synchronize account map it…
fix(profile-subscriptions): synchronize account map iteration against concurrent removal nostr:nevent1qqsxuv9uf8nvwg5cdfln3r82g6jjqx47kfvpgz7yxcstj4sxf5d3jvqpz3mhxue69uhhyetvv9ujumn8d96zuer9wcql9j3h PR-Author: greenart7c3 nostr:npub1w4uswmv6lu9yel005l3qgheysmr7tk9uvwluddznju3nuxalevvs2d0jr5 PR description: Fixes the NoSuchElementException reported in nevent1qqszh6nmwv9nl6wt6zg8qm8azhqlhk76hemgn5us0pg4cx7twmayv6cgn4dga updateFilters() snapshotted the accounts map (accounts.values.toList()) while closeSub() and the EOSE path concurrently removed entries. A weakly-consistent ConcurrentHashMap iterator can run off the end when the map shrinks mid-iteration, throwing NoSuchElementException out of updateFilters(). Fix: iteration and mutation of the accounts map are now mutually exclusive 342200224 updateFilters() copies the value list under synchronized(accounts) and iterates the snapshot outside the lock; all mutation sites (put in updateFilter, remove in closeSub(account), clear in closeSub()) take the same monitor. Single-key lookups stay lock-free. Verified: ktlintCheck, lint, full test suite (all variants) green; ProfileSubscriptionTest concurrency tests 10/10 clean repeated runs.
This commit is contained in:
@@ -68,6 +68,10 @@ class ProfileSubscription(
|
|||||||
|
|
||||||
// hexKey -> the cached Account instance a live composable cares about. Updating the
|
// hexKey -> the cached Account instance a live composable cares about. Updating the
|
||||||
// StateFlows on these instances is what reflects fresh metadata in the UI.
|
// StateFlows on these instances is what reflects fresh metadata in the UI.
|
||||||
|
// Iteration (updateFilters) is wrapped in synchronized(this map) and every mutation
|
||||||
|
// (put/remove/clear) takes the same monitor, so the weakly-consistent CHM iterator
|
||||||
|
// can never run off the end and throw NoSuchElementException when the map shrinks
|
||||||
|
// mid-iteration. Single-key reads stay lock-free.
|
||||||
private val accounts = ConcurrentHashMap<String, Account>()
|
private val accounts = ConcurrentHashMap<String, Account>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@@ -148,7 +152,7 @@ class ProfileSubscription(
|
|||||||
val interval = Amber.instance.settings.profileFetchInterval
|
val interval = Amber.instance.settings.profileFetchInterval
|
||||||
if (interval == ProfileFetchInterval.NEVER) return
|
if (interval == ProfileFetchInterval.NEVER) return
|
||||||
|
|
||||||
accounts[account.hexKey] = account
|
synchronized(accounts) { accounts[account.hexKey] = account }
|
||||||
|
|
||||||
val shouldFetch = if (interval == ProfileFetchInterval.ALWAYS) {
|
val shouldFetch = if (interval == ProfileFetchInterval.ALWAYS) {
|
||||||
true
|
true
|
||||||
@@ -228,14 +232,15 @@ class ProfileSubscription(
|
|||||||
* Re-runs the fetch for every currently tracked account. Call when the relay list changes.
|
* Re-runs the fetch for every currently tracked account. Call when the relay list changes.
|
||||||
*/
|
*/
|
||||||
suspend fun updateFilters() {
|
suspend fun updateFilters() {
|
||||||
accounts.values.toList().forEach { updateFilter(it) }
|
val snapshot = synchronized(accounts) { accounts.values.toList() }
|
||||||
|
snapshot.forEach { updateFilter(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stops updates for a single [account] (call when the composable leaves composition).
|
* Stops updates for a single [account] (call when the composable leaves composition).
|
||||||
*/
|
*/
|
||||||
fun closeSub(account: Account) {
|
fun closeSub(account: Account) {
|
||||||
accounts.remove(account.hexKey)
|
synchronized(accounts) { accounts.remove(account.hexKey) }
|
||||||
relayListSubIds.remove(account.hexKey)?.let { unsubscribe(it) }
|
relayListSubIds.remove(account.hexKey)?.let { unsubscribe(it) }
|
||||||
subIds.remove(account.hexKey)?.let { unsubscribe(it) }
|
subIds.remove(account.hexKey)?.let { unsubscribe(it) }
|
||||||
}
|
}
|
||||||
@@ -252,7 +257,7 @@ class ProfileSubscription(
|
|||||||
relaysPerSubId.clear()
|
relaysPerSubId.clear()
|
||||||
subIds.clear()
|
subIds.clear()
|
||||||
relayListSubIds.clear()
|
relayListSubIds.clear()
|
||||||
accounts.clear()
|
synchronized(accounts) { accounts.clear() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Default profile relays from the settings plus the user's own saved relay list. */
|
/** Default profile relays from the settings plus the user's own saved relay list. */
|
||||||
|
|||||||
Reference in New Issue
Block a user