Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package io.getstream.android.core.api.model.retry

import androidx.annotation.IntRange
import io.getstream.android.core.annotations.StreamInternalApi

/**
Expand Down Expand Up @@ -49,11 +50,11 @@ import io.getstream.android.core.annotations.StreamInternalApi
@ConsistentCopyVisibility
public data class StreamRetryPolicy
private constructor(
val minRetries: Int,
val maxRetries: Int,
val minBackoffMills: Long,
val maxBackoffMills: Long,
val initialDelayMillis: Long,
@IntRange(from = 1) val minRetries: Int,
@IntRange(from = 1) val maxRetries: Int,
@IntRange(from = 0) val minBackoffMills: Long,
@IntRange(from = 0) val maxBackoffMills: Long,
@IntRange(from = 0) val initialDelayMillis: Long,
val giveUpFunction: (retry: Int, cause: Throwable) -> Boolean,
val nextBackOffDelayFunction: (retry: Int, previousDelay: Long) -> Long,
) {
Expand Down Expand Up @@ -85,11 +86,11 @@ private constructor(
* @param giveUp Custom predicate to override the default “> maxRetries”.
*/
public fun exponential(
minRetries: Int = 1,
maxRetries: Int = 5,
backoffStepMillis: Long = 250,
maxBackoffMillis: Long = 15_000,
initialDelayMillis: Long = 0,
@IntRange(from = 1) minRetries: Int = 1,
@IntRange(from = 1) maxRetries: Int = 5,
@IntRange(from = 0) backoffStepMillis: Long = 250,
@IntRange(from = 0) maxBackoffMillis: Long = 15_000,
@IntRange(from = 0) initialDelayMillis: Long = 0,
giveUp: (Int, Throwable) -> Boolean = { retry, _ -> retry > maxRetries },
): StreamRetryPolicy =
StreamRetryPolicy(
Expand All @@ -105,7 +106,7 @@ private constructor(
.coerceIn(backoffStepMillis, maxBackoffMillis)
},
)
.validate()
.also { it.requireValid() }

/**
* Creates a **linear back-off** policy.
Expand All @@ -125,11 +126,11 @@ private constructor(
* Parameter semantics match [exponential].
*/
public fun linear(
minRetries: Int = 1,
maxRetries: Int = 5,
backoffStepMillis: Long = 250,
maxBackoffMillis: Long = 15_000,
initialDelayMillis: Long = 0,
@IntRange(from = 1) minRetries: Int = 1,
@IntRange(from = 1) maxRetries: Int = 5,
@IntRange(from = 0) backoffStepMillis: Long = 250,
@IntRange(from = 0) maxBackoffMillis: Long = 15_000,
@IntRange(from = 0) initialDelayMillis: Long = 0,
giveUp: (Int, Throwable) -> Boolean = { retry, _ -> retry > maxRetries },
): StreamRetryPolicy =
StreamRetryPolicy(
Expand All @@ -143,7 +144,7 @@ private constructor(
(prev + backoffStepMillis).coerceAtMost(maxBackoffMillis)
},
)
.validate()
.also { it.requireValid() }

/**
* **Fixed-delay back-off**: every retry waits exactly [delayMillis], clamped to
Expand All @@ -157,11 +158,11 @@ private constructor(
* ```
*/
public fun fixed(
minRetries: Int = 1,
maxRetries: Int = 5,
delayMillis: Long = 500,
maxBackoffMillis: Long = 15_000,
initialDelayMillis: Long = 0,
@IntRange(from = 1) minRetries: Int = 1,
@IntRange(from = 1) maxRetries: Int = 5,
@IntRange(from = 0) delayMillis: Long = 500,
@IntRange(from = 0) maxBackoffMillis: Long = 15_000,
@IntRange(from = 0) initialDelayMillis: Long = 0,
giveUp: (Int, Throwable) -> Boolean = { retry, _ -> retry > maxRetries },
): StreamRetryPolicy =
StreamRetryPolicy(
Expand All @@ -175,7 +176,7 @@ private constructor(
delayMillis.coerceAtMost(maxBackoffMillis)
},
)
.validate()
.also { it.requireValid() }

/**
* **Custom builder** – explicitly specify every parameter while still benefiting from the
Expand Down Expand Up @@ -221,17 +222,26 @@ private constructor(
nextDelay(attempt, prev).coerceIn(minBackoffMills, maxBackoffMills)
},
)
.validate()
.also { it.requireValid() }
}

private fun StreamRetryPolicy.validate(): StreamRetryPolicy {
require(minRetries > 0) { "minRetries must be ≥ 0" }
require(maxRetries >= minRetries) { "maxRetries must be ≥ minRetries" }
require(minBackoffMills >= 0) { "minBackoffMills must be ≥ 0" }
require(maxBackoffMills >= minBackoffMills) {
"maxBackoffMills must be ≥ minBackoffMills"
}
require(initialDelayMillis >= 0) { "initialDelayMillis must be ≥ 0" }
return this
/**
* Validates this policy's invariants. Called by factory methods at creation time and by the
* retry processor at execution time (defense-in-depth).
*
* @throws IllegalArgumentException if any invariant is violated.
*/
internal fun requireValid() {
require(minRetries > 0) { "minRetries must be > 0, got $minRetries" }
require(maxRetries >= minRetries) {
"maxRetries must be ≥ minRetries, got $maxRetries < $minRetries"
}
require(minBackoffMills >= 0) { "minBackoffMills must be ≥ 0, got $minBackoffMills" }
require(maxBackoffMills >= minBackoffMills) {
"maxBackoffMills must be ≥ minBackoffMills, got $maxBackoffMills < $minBackoffMills"
}
require(initialDelayMillis >= 0) {
"initialDelayMillis must be ≥ 0, got $initialDelayMillis"
}
Comment thread
aleksandar-apostolov marked this conversation as resolved.
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@
import kotlinx.coroutines.delay

internal class StreamRetryProcessorImpl(private val logger: StreamLogger) : StreamRetryProcessor {
override suspend fun <T> retry(

Check failure on line 28 in stream-android-core/src/main/java/io/getstream/android/core/internal/processing/StreamRetryProcessorImpl.kt

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to reduce its Cognitive Complexity from 17 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=GetStream_stream-core-android&issues=AZ20amkRoR2HN3lWgxV5&open=AZ20amkRoR2HN3lWgxV5&pullRequest=56
policy: StreamRetryPolicy,
block: suspend (attempt: StreamRetryAttemptInfo) -> T,
): Result<T> = runCatchingCancellable {
policy.requireValid()
var delayMs = policy.initialDelayMillis
var attempt = 1
var previousError: Throwable? = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,8 @@ class StreamRetryProcessorImplTest {

val err = res.exceptionOrNull()
assertTrue(res.isFailure)
assertEquals(IllegalStateException::class.java, err!!::class.java)
assertTrue(err.message!!.contains("Check your policy"))
assertEquals(IllegalArgumentException::class.java, err!!::class.java)
assertTrue(err.message!!.contains("maxRetries must be"))
}

@Test
Expand Down
Loading