mirror of
https://github.com/pezkuwichain/pezkuwi-wallet-android.git
synced 2026-04-22 03:18:02 +00:00
fix: prevent staking dashboard infinite loading
- Add maxAttempts parameter to retryUntilDone (default unlimited for backward compat), use 5 retries for staking stats fetch - Catch fetchStakingStats failure in dashboard update system and fallback to empty stats instead of hanging forever - Restore stale scope detection in ComputationalCache so cancelled aggregate scopes are recreated instead of returning stale entries
This commit is contained in:
+11
-6
@@ -13,6 +13,7 @@ import kotlinx.coroutines.cancel
|
|||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
@@ -72,16 +73,20 @@ internal class RealComputationalCache : ComputationalCache, CoroutineScope by Co
|
|||||||
cachedAction: AwaitableConstructor<T>
|
cachedAction: AwaitableConstructor<T>
|
||||||
): T {
|
): T {
|
||||||
val awaitable = mutex.withLock {
|
val awaitable = mutex.withLock {
|
||||||
if (key in memory) {
|
val existing = memory[key]
|
||||||
|
if (existing != null && existing.aggregateScope.isActive) {
|
||||||
Log.d(LOG_TAG, "Key $key requested - already present")
|
Log.d(LOG_TAG, "Key $key requested - already present")
|
||||||
|
|
||||||
val entry = memory.getValue(key)
|
existing.dependents += scope
|
||||||
|
|
||||||
entry.dependents += scope
|
existing.awaitable
|
||||||
|
|
||||||
entry.awaitable
|
|
||||||
} else {
|
} else {
|
||||||
Log.d(LOG_TAG, "Key $key requested - creating new operation")
|
if (existing != null) {
|
||||||
|
Log.d(LOG_TAG, "Key $key requested - stale (aggregateScope cancelled), recreating")
|
||||||
|
memory.remove(key)
|
||||||
|
} else {
|
||||||
|
Log.d(LOG_TAG, "Key $key requested - creating new operation")
|
||||||
|
}
|
||||||
|
|
||||||
val aggregateScope = CoroutineScope(Dispatchers.Default)
|
val aggregateScope = CoroutineScope(Dispatchers.Default)
|
||||||
val awaitable = cachedAction(aggregateScope)
|
val awaitable = cachedAction(aggregateScope)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import kotlinx.coroutines.delay
|
|||||||
|
|
||||||
suspend inline fun <T> retryUntilDone(
|
suspend inline fun <T> retryUntilDone(
|
||||||
retryStrategy: ReconnectStrategy = LinearReconnectStrategy(step = 500L),
|
retryStrategy: ReconnectStrategy = LinearReconnectStrategy(step = 500L),
|
||||||
|
maxAttempts: Int = Int.MAX_VALUE,
|
||||||
block: () -> T,
|
block: () -> T,
|
||||||
): T {
|
): T {
|
||||||
var attempt = 0
|
var attempt = 0
|
||||||
@@ -17,10 +18,14 @@ suspend inline fun <T> retryUntilDone(
|
|||||||
if (blockResult.isSuccess) {
|
if (blockResult.isSuccess) {
|
||||||
return blockResult.requireValue()
|
return blockResult.requireValue()
|
||||||
} else {
|
} else {
|
||||||
Log.e("RetryUntilDone", "Failed to execute retriable operation:", blockResult.requireException())
|
|
||||||
|
|
||||||
attempt++
|
attempt++
|
||||||
|
|
||||||
|
if (attempt >= maxAttempts) {
|
||||||
|
throw blockResult.requireException()
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.e("RetryUntilDone", "Failed to execute retriable operation (attempt $attempt):", blockResult.requireException())
|
||||||
|
|
||||||
delay(retryStrategy.getTimeForReconnect(attempt))
|
delay(retryStrategy.getTimeForReconnect(attempt))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -39,7 +39,7 @@ class RealStakingStatsDataSource(
|
|||||||
stakingAccounts: StakingAccounts,
|
stakingAccounts: StakingAccounts,
|
||||||
stakingChains: List<Chain>
|
stakingChains: List<Chain>
|
||||||
): MultiChainStakingStats = withContext(Dispatchers.IO) {
|
): MultiChainStakingStats = withContext(Dispatchers.IO) {
|
||||||
retryUntilDone {
|
retryUntilDone(maxAttempts = 5) {
|
||||||
val globalConfig = globalConfigDataSource.getGlobalConfig()
|
val globalConfig = globalConfigDataSource.getGlobalConfig()
|
||||||
val chainsByEndpoint = splitChainsByEndpoint(stakingChains, globalConfig)
|
val chainsByEndpoint = splitChainsByEndpoint(stakingChains, globalConfig)
|
||||||
|
|
||||||
|
|||||||
+8
-1
@@ -127,9 +127,16 @@ class RealStakingDashboardUpdateSystem(
|
|||||||
.onEach { latestOffChainSyncIndex.value = it.index }
|
.onEach { latestOffChainSyncIndex.value = it.index }
|
||||||
.throttleLast(offChainSyncDebounceRate)
|
.throttleLast(offChainSyncDebounceRate)
|
||||||
.mapLatest { (index, stakingAccounts) ->
|
.mapLatest { (index, stakingAccounts) ->
|
||||||
|
val stats = runCatching {
|
||||||
|
stakingStatsDataSource.fetchStakingStats(stakingAccounts, stakingChains)
|
||||||
|
}.getOrElse {
|
||||||
|
Log.d("StakingDashboardUpdateSystem", "Failed to fetch staking stats after retries", it)
|
||||||
|
emptyMap()
|
||||||
|
}
|
||||||
|
|
||||||
MultiChainOffChainSyncResult(
|
MultiChainOffChainSyncResult(
|
||||||
index = index,
|
index = index,
|
||||||
multiChainStakingStats = stakingStatsDataSource.fetchStakingStats(stakingAccounts, stakingChains),
|
multiChainStakingStats = stats,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user