Programming Language/Kotlin

[Kotlin] Flow Retry 연산자

Tenacity_Dev 2024. 3. 3. 01:59
728x90

Kotlin Flow에서 연산자를 사용하여 작업을 재시도하는 방법에 대해서는 두 연산자가 있다.

  • retryWhen
  • retry

 

retryWhen

retryWhen의 정의

fun <T> Flow<T>.retryWhen(predicate: suspend FlowCollector<T>.(cause: Throwable, attempt: Long) -> Boolean): Flow<T>

 

그리고 다음과 같이 사용한다.

.retryWhen { cause, attempt ->

}

 

여기에는 다음과 같은 두 가지 매개변수가 있다.

  • cause : cause는 예외의 원인을 제공하는 변수이다. 만약에 함수가 진행하면서 예외가 발생한다면 어떤한 예외인지를 나타낸다.
  • attempt : 현재 시도를 나타내는 변수이다. 0부터 시작한다.

예를 들어, 작업을 시작할 때 예외가 있다면 원인(예외)과 시도(0)를 받게 된다.

retryWhen은 predicate 함수를 사용하여 재시도 여부를 결정한다.
술어 함수가 참으로 돌아오면, 재시도를 하고 그렇지 않으면 재시도하지 않는다.

 

예시 코드

.retryWhen { cause, attempt ->
    if (cause is IOException && attempt < 3) {
        delay(2000)
        return@retryWhen true
    } else {
        return@retryWhen false
    }
}

이 경우 원인이 IO 예외이고 시도 횟수가 3회 미만일 때 참으로 돌아오는 것이다.
따라서 조건이 충족된 경우에만 재시도되는 것이다.

 

참고: predicate 함수가 정지 함수이므로, 그로부터 다른 정지 함수를 호출할 수 있다.
상기 코드에서 알아차린 경우, 우리는 지연(2000)을 호출하여 2초의 지연 후에만 재시도한다.

 

retry

retry함수의 정의

fun <T> Flow<T>.retry(
    retries: Long = Long.MAX_VALUE,
    predicate: suspend (cause: Throwable) -> Boolean = { true }
): Flow<T>

 

아래는 Kotlin Flow 소스 코드의 전체 블록이다.

fun <T> Flow<T>.retry(
    retries: Long = Long.MAX_VALUE,
    predicate: suspend (cause: Throwable) -> Boolean = { true }
): Flow<T> {
    require(retries > 0) { "Expected positive amount of retries, but had $retries" }
    return retryWhen { cause, attempt -> attempt < retries && predicate(cause) }
}

 

retry함수를 실제로 보면 retryWhen을 내부적으로 호출하는 것을 볼 수있다.

 

retry 함수에는 기본 인수가 있다.

  • retries를 전달하지 않으면 Long.MAX_VALUE를 사용한다.
  • predicate를 전달하지 않으면 true가 된다.

예를 들어, 아래와 같은 경우

.retry()

작업이 성공적으로 완료할때까지 게속 재시도 한다.

 

.retry(3)

3번만 재시도합니다.

 

.retry(retries = 3) { cause ->
    if (cause is IOException) {
        delay(2000)
        return@retry true
    } else {
        return@retry false
    }
}

여기서는 위의 retryWhen을 사용하여 수행한 것과 매우 유사해집니다.
여기서는 원인이 IOException일 때 참으로 돌아오는 것이다. 따라서 원인이 IOException일 때만 재시도됩니다.
지연(2000)을 호출하여 2초의 지연 후에만 재시도한다.

 

아래는 장기 실행 작업을 시뮬레이션 하는 코드이다.

private fun doLongRunningTask(): Flow<Int> {
    return flow {
        // your code for doing a long running task
        // Added delay, random number, and exception to simulate
        delay(2000)
        val randomNumber = (0..2).random()
        if (randomNumber == 0) {
            throw IOException()
        } else if (randomNumber == 1) {
            throw IndexOutOfBoundsException()
        }
        delay(2000)
        emit(0)
    }
}

 

retry연산자를 사용한다면

viewModelScope.launch {
    doLongRunningTask()
        .flowOn(Dispatchers.Default)
        .retry(retries = 3) { cause ->
            if (cause is IOException) {
                delay(2000)
                return@retry true
            } else {
                return@retry false
            }
        }
        .catch {
           // error
        }
        .collect {
            // success
        }
}

 

마찬가지로 retryWhen연산자를 사용한다면

viewModelScope.launch {
    doLongRunningTask()
        .flowOn(Dispatchers.Default)
        .retryWhen { cause, attempt ->
            if (cause is IOException && attempt < 3) {
                delay(2000)
                return@retryWhen true
            } else {
                return@retryWhen false
            }
        }
        .catch {
            // error
        }
        .collect {
            // success
        }
}

우리가 볼 때마다 2초 단위의 지연을 추가하지만 실제 사용 사례에서는 지수 백오프를 사용하여 지연을 추가한다.

 

지수 백오프 지연을 사용하는 retry연산자

지수 백오프를 사용하여 지연에 대한 코드를 추가한 후

viewModelScope.launch {
    var currentDelay = 1000L
    val delayFactor = 2
    doLongRunningTask()
        .flowOn(Dispatchers.Default)
        .retry(retries = 3) { cause ->
            if (cause is IOException) {
                delay(currentDelay)
                currentDelay = (currentDelay * delayFactor)
                return@retry true
            } else {
                return@retry false
            }
        }
        .catch {
            // error
        }
        .collect {
            // success
        }
}

여기서는 두가지 변수를 만들었다.

  • currentDelay : 이는 현재 재시도에 사용될 지연을 나타낸다.
  • DelayFactor : DelayFactor를 사용하여 currentDelay와 곱하여 다음 재시도에 대한 지연 시간을 늘립니다.

 

참고

https://amitshekhar.me/blog/retry-operator-in-kotlin-flow

 

Retry Operator in Kotlin Flow

In this blog, we will learn about the Retry Operator in Kotlin Flow.

amitshekhar.me

 

 

728x90