🔄 Retry & Backoff

KNET oferuje różne strategie automatycznego ponawiania requestów przy błędach.

Prosty Retry

import rip.nerd.kitsunenet.interceptor.KNETRetryInterceptor

val client = KNETClient.builder()
    .addInterceptor(KNETRetryInterceptor(
        maxRetries = 3,
        initialDelayMs = 1000
    ))
    .build()

// Request będzie ponawiany do 3 razy
// z exponential backoff od 1s

// Metody fabryczne
KNETRetryInterceptor.simple(maxRetries = 3)
KNETRetryInterceptor.aggressive(maxRetries = 5)

Exponential Backoff

val client = KNETClient.builder()
    .addInterceptor(KNETExponentialBackoffInterceptor(
        maxRetries = 5,
        initialDelayMs = 100,     // 100ms
        maxDelayMs = 30_000,      // Max 30s
        multiplier = 2.0          // x2 każdy retry
    ))
    .build()

// Opóźnienia: ~100ms → ~200ms → ~400ms → ~800ms → ~1600ms

Jitter (losowość)

val client = KNETClient.builder()
    .addInterceptor(KNETExponentialBackoffInterceptor(
        maxRetries = 5,
        initialDelayMs = 100,
        maxDelayMs = 30_000,
        multiplier = 2.0,
        jitterFraction = 0.1  // 10% losowości
    ))
    .build()

// Opóźnienia z jitter: ~100ms → ~200ms → ~400ms → ...
// Zapobiega problemowi "thundering herd"

Konfiguracja - co ponawiać

val interceptor = KNETRetryInterceptor(
    maxRetries = 3,
    initialDelayMs = 1000,
    maxDelayMs = 30_000,
    multiplier = 2.0,

    // Statusy HTTP do retry
    retryOnErrors = setOf(
        408,  // Request Timeout
        429,  // Too Many Requests
        500,  // Internal Server Error
        502,  // Bad Gateway
        503,  // Service Unavailable
        504   // Gateway Timeout
    ),

    // Retry przy błędach sieciowych (SocketTimeout, UnknownHost itp.)
    retryOnNetworkError = true,

    // Callback przy każdym retry
    onRetry = { attempt, error, response ->
        Log.w("Retry", "Próba $attempt, błąd: ${error?.message}")
    }
)

Retry callbacks

val interceptor = KNETRetryInterceptor(
    maxRetries = 3,
    onRetry = { attempt, error, response ->
        Log.w("Retry", "Próba $attempt")
        Log.w("Retry", "Błąd: ${error?.message}")
        Log.w("Retry", "Status: ${response?.statusCode}")

        // Analytics
        analytics.log("api_retry", mapOf(
            "attempt" to attempt,
            "error" to error?.javaClass?.simpleName
        ))
    }
)

Zaawansowany retry z KNETAutoRetryInterceptor

import rip.nerd.kitsunenet.interceptor.KNETAutoRetryInterceptor
import rip.nerd.kitsunenet.util.KNETRetryPolicies

val client = KNETClient.builder()
    .addInterceptor(KNETAutoRetryInterceptor(
        policy = KNETRetryPolicies.standard,
        onRetry = { attempt, error ->
            Log.d("KNET", "Retry #$attempt: ${error?.message}")
        },
        onGiveUp = { attempts, lastError ->
            Log.e("KNET", "Poddałem się po $attempts próbach")
        }
    ))
    .build()

Przykład: Resilient fetch

class ApiService(private val client: KNETClient) {

    private suspend fun <T> withRetry(
        maxRetries: Int,
        initialDelay: Long,
        exponential: Boolean = false,
        block: suspend () -> T
    ): T {
        var lastError: Exception? = null
        var currentDelay = initialDelay

        repeat(maxRetries) { attempt ->
            try {
                return block()
            } catch (e: Exception) {
                lastError = e
                Log.w("API", "Retry ${attempt + 1}/$maxRetries: ${e.message}")

                if (attempt < maxRetries - 1) {
                    kotlinx.coroutines.delay(currentDelay)
                    if (exponential) {
                        currentDelay = minOf(currentDelay * 2, 30_000)
                    }
                }
            }
        }

        throw lastError ?: Exception("Max retries exceeded")
    }
}

Porównanie strategii

Strategia Opóźnienia Zastosowanie
Simple 1s, 1s, 1s Szybkie błędy serwera
Exponential 1s, 2s, 4s, 8s Przeciążony serwer
Exp + Jitter ~1s, ~2s, ~4s Wielu równoczesnych klientów
Linear 1s, 2s, 3s, 4s Stopniowe odciążanie serwera

📚 Zobacz też