Config lock contention and loading timeout issue

This commit is contained in:
SessionHero01 2024-10-08 15:53:22 +11:00
parent 966b8e0d7f
commit db8784f0db
No known key found for this signature in database
2 changed files with 41 additions and 32 deletions

View File

@ -52,8 +52,11 @@ import org.thoughtcrime.securesms.database.ConfigDatabase
import org.thoughtcrime.securesms.database.LokiThreadDatabase import org.thoughtcrime.securesms.database.LokiThreadDatabase
import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.database.ThreadDatabase
import org.thoughtcrime.securesms.groups.GroupManager import org.thoughtcrime.securesms.groups.GroupManager
import java.util.concurrent.locks.ReentrantReadWriteLock
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
import kotlin.concurrent.read
import kotlin.concurrent.write
@Singleton @Singleton
@ -78,8 +81,8 @@ class ConfigFactory @Inject constructor(
System.loadLibrary("session_util") System.loadLibrary("session_util")
} }
private val userConfigs = HashMap<AccountId, UserConfigsImpl>() private val userConfigs = HashMap<AccountId, Pair<ReentrantReadWriteLock, UserConfigsImpl>>()
private val groupConfigs = HashMap<AccountId, GroupConfigsImpl>() private val groupConfigs = HashMap<AccountId, Pair<ReentrantReadWriteLock, GroupConfigsImpl>>()
private val _configUpdateNotifications = MutableSharedFlow<ConfigUpdateNotification>( private val _configUpdateNotifications = MutableSharedFlow<ConfigUpdateNotification>(
extraBufferCapacity = 5, // The notifications are normally important so we can afford to buffer a few extraBufferCapacity = 5, // The notifications are normally important so we can afford to buffer a few
@ -97,21 +100,39 @@ class ConfigFactory @Inject constructor(
"No logged in user" "No logged in user"
} }
override fun <T> withUserConfigs(cb: (UserConfigs) -> T): T { private fun ensureUserConfigsInitialized(): Pair<ReentrantReadWriteLock, UserConfigsImpl> {
val userAccountId = requiresCurrentUserAccountId() val userAccountId = requiresCurrentUserAccountId()
val configs = synchronized(userConfigs) {
return synchronized(userConfigs) {
userConfigs.getOrPut(userAccountId) { userConfigs.getOrPut(userAccountId) {
UserConfigsImpl( ReentrantReadWriteLock() to UserConfigsImpl(
requiresCurrentUserED25519SecKey(), userEd25519SecKey = requiresCurrentUserED25519SecKey(),
userAccountId, userAccountId = userAccountId,
threadDb = threadDb, threadDb = threadDb,
configDatabase = configDatabase, configDatabase = configDatabase,
storage = storage.get() storage = storage.get()
) )
} }
} }
}
return synchronized(configs) { private fun ensureGroupConfigsInitialized(groupId: AccountId): Pair<ReentrantReadWriteLock, GroupConfigsImpl> {
val groupAdminKey = getClosedGroup(groupId)?.adminKey
return synchronized(groupConfigs) {
groupConfigs.getOrPut(groupId) {
ReentrantReadWriteLock() to GroupConfigsImpl(
userEd25519SecKey = requiresCurrentUserED25519SecKey(),
groupAccountId = groupId,
groupAdminKey = groupAdminKey,
configDatabase = configDatabase
)
}
}
}
override fun <T> withUserConfigs(cb: (UserConfigs) -> T): T {
val (lock, configs) = ensureUserConfigsInitialized()
return lock.read {
cb(configs) cb(configs)
} }
} }
@ -122,8 +143,9 @@ class ConfigFactory @Inject constructor(
* @param cb A function that takes a [UserConfigsImpl] and returns a pair of the result of the operation and a boolean indicating if the configs were changed. * @param cb A function that takes a [UserConfigsImpl] and returns a pair of the result of the operation and a boolean indicating if the configs were changed.
*/ */
private fun <T> doWithMutableUserConfigs(cb: (UserConfigsImpl) -> Pair<T, Boolean>): T { private fun <T> doWithMutableUserConfigs(cb: (UserConfigsImpl) -> Pair<T, Boolean>): T {
val (result, changed) = withUserConfigs { configs -> val (lock, configs) = ensureUserConfigsInitialized()
cb(configs as UserConfigsImpl) val (result, changed) = lock.write {
cb(configs)
} }
if (changed) { if (changed) {
@ -160,25 +182,9 @@ class ConfigFactory @Inject constructor(
} }
override fun <T> withGroupConfigs(groupId: AccountId, cb: (GroupConfigs) -> T): T { override fun <T> withGroupConfigs(groupId: AccountId, cb: (GroupConfigs) -> T): T {
val groupAdminKey = getClosedGroup(groupId)?.adminKey val (lock, configs) = ensureGroupConfigsInitialized(groupId)
val configs = synchronized(groupConfigs) { return lock.read {
var value = groupConfigs[groupId]
if (value == null || value.groupKeys.admin() != (groupAdminKey != null)) {
// No existing configs or existing configs have different admin settings with what we currently have
// Create a new group configs
value = GroupConfigsImpl(
requiresCurrentUserED25519SecKey(),
groupId,
groupAdminKey,
configDatabase
).also { groupConfigs[groupId] = it }
}
value
}
return synchronized(configs) {
cb(configs) cb(configs)
} }
} }
@ -188,11 +194,14 @@ class ConfigFactory @Inject constructor(
recreateConfigInstances: Boolean, recreateConfigInstances: Boolean,
cb: (GroupConfigsImpl) -> Pair<T, Boolean>): T { cb: (GroupConfigsImpl) -> Pair<T, Boolean>): T {
if (recreateConfigInstances) { if (recreateConfigInstances) {
synchronized(groupConfigs) {
groupConfigs.remove(groupId) groupConfigs.remove(groupId)
} }
}
val (result, changed) = withGroupConfigs(groupId) { configs -> val (lock, configs) = ensureGroupConfigsInitialized(groupId)
cb(configs as GroupConfigsImpl) val (result, changed) = lock.write {
cb(configs)
} }
if (changed) { if (changed) {

View File

@ -80,7 +80,7 @@ internal class LoadingViewModel @Inject constructor(
!configs.userProfile.getName().isNullOrEmpty() !configs.userProfile.getName().isNullOrEmpty()
} }
} }
// .timeout(TIMEOUT_TIME) .timeout(TIMEOUT_TIME)
.first() .first()
onSuccess() onSuccess()
} catch (e: Exception) { } catch (e: Exception) {