feat: add retry with exponential backoff to all repositories
- New: RetryHelper.kt — retryOnTransientError(maxRetries=2, baseDelay=1s) - Wraps in RefreshBalanceUseCase — single dispatch point for all 5 services - Retries on Network and 5xx ServerError - 401/403 (InvalidCredential), 429 (RateLimited), ParseError — no retry
This commit is contained in:
parent
f2e5eedaa4
commit
220a74492a
@ -0,0 +1,39 @@
|
||||
package com.rainy.token.data.repository
|
||||
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlin.math.pow
|
||||
|
||||
/**
|
||||
* 通用重试包装。
|
||||
*
|
||||
* 对 [RepositoryError.Network] 和 [RepositoryError.ServerError] 执行指数退避重试。
|
||||
* 401/403(InvalidCredential)、429(RateLimited)、ParseError 等不重试。
|
||||
*
|
||||
* @param maxRetries 最大重试次数(不含首次),默认 2
|
||||
* @param baseDelayMs 退避基数,默认 1000ms(第二次 = 2000ms)
|
||||
* @param block 实际请求逻辑
|
||||
*/
|
||||
suspend fun <T> retryOnTransientError(
|
||||
maxRetries: Int = 2,
|
||||
baseDelayMs: Long = 1000L,
|
||||
block: suspend () -> Result<T>
|
||||
): Result<T> {
|
||||
var lastResult = block()
|
||||
if (lastResult.isSuccess) return lastResult
|
||||
|
||||
repeat(maxRetries) { attempt ->
|
||||
val error = lastResult.exceptionOrNull()
|
||||
val shouldRetry = when (error) {
|
||||
is RepositoryError.Network -> true
|
||||
is RepositoryError.ServerError -> error.code in 500..599
|
||||
else -> false
|
||||
}
|
||||
if (!shouldRetry) return lastResult
|
||||
|
||||
val backoffMs = baseDelayMs * (2.0.pow(attempt).toLong())
|
||||
delay(backoffMs)
|
||||
lastResult = block()
|
||||
if (lastResult.isSuccess) return lastResult
|
||||
}
|
||||
return lastResult
|
||||
}
|
||||
@ -5,6 +5,7 @@ import com.rainy.token.data.repository.CommandCodeGoRepository
|
||||
import com.rainy.token.data.repository.DeepSeekRepository
|
||||
import com.rainy.token.data.repository.OllamaRepository
|
||||
import com.rainy.token.data.repository.OpenCodeGoRepository
|
||||
import com.rainy.token.data.repository.retryOnTransientError
|
||||
import com.rainy.token.domain.model.ServiceBalance
|
||||
import com.rainy.token.domain.service.ServiceType
|
||||
import javax.inject.Inject
|
||||
@ -19,6 +20,8 @@ import javax.inject.Provider
|
||||
* 注入同一 UseCase 时的"could not be resolved"误报(KSP 已知 issue)。
|
||||
* Provider 让 Hilt 推迟创建 Repository 实例到第一次 .get() 时,KSP 不需要在
|
||||
* 编译期解析所有构造器签名。
|
||||
* - **重试策略**:对 Network 和 5xx ServerError 自动指数退避重试(最多 2 次),
|
||||
* 401/403/429/ParseError 不重试。
|
||||
*/
|
||||
class RefreshBalanceUseCase @Inject constructor(
|
||||
private val deepSeekRepositoryProvider: Provider<DeepSeekRepository>,
|
||||
@ -28,10 +31,10 @@ class RefreshBalanceUseCase @Inject constructor(
|
||||
private val ollamaRepositoryProvider: Provider<OllamaRepository>
|
||||
) {
|
||||
suspend operator fun invoke(service: ServiceType): Result<ServiceBalance> = when (service) {
|
||||
ServiceType.DEEPSEEK -> deepSeekRepositoryProvider.get().fetchBalance()
|
||||
ServiceType.OPENCODE_GO -> openCodeGoRepositoryProvider.get().fetchBalance()
|
||||
ServiceType.COMMANDCODE_GO -> commandCodeGoRepositoryProvider.get().fetchBalance()
|
||||
ServiceType.CODEX -> codexRepositoryProvider.get().fetchBalance()
|
||||
ServiceType.OLLAMA -> ollamaRepositoryProvider.get().fetchBalance()
|
||||
ServiceType.DEEPSEEK -> retryOnTransientError { deepSeekRepositoryProvider.get().fetchBalance() }
|
||||
ServiceType.OPENCODE_GO -> retryOnTransientError { openCodeGoRepositoryProvider.get().fetchBalance() }
|
||||
ServiceType.COMMANDCODE_GO -> retryOnTransientError { commandCodeGoRepositoryProvider.get().fetchBalance() }
|
||||
ServiceType.CODEX -> retryOnTransientError { codexRepositoryProvider.get().fetchBalance() }
|
||||
ServiceType.OLLAMA -> retryOnTransientError { ollamaRepositoryProvider.get().fetchBalance() }
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user