delay()

코틀린의 delay 함수는 코루틴을 지연시키는 함수입니다. 이는 스레드를 차단하지 않고 코루틴이 일정 시간 동안 대기하도록 합니다.

fun main() = runBlocking {
    println("Start")
    delay(1000L)
    println("End")
}

위 코드에서 delay(1000L)은 1초 동안 코루틴을 지연시키고, 그 이후에 "End"를 출력합니다.

delay() 함수는 suspend 함수로, 내부적으로 코루틴을 일시 중단(pause)시키고, 지정된 시간이 경과한 후에 코루틴을 재개합니다.

 

withTimeout

withTimeout 함수는 주어진 시간 내에 완료되지 않는 작업을 취소하는 함수입니다. withTimeout 블록 내에서 작업이 지정된 시간 내에 완료되지 않으면 TimeoutCancellationException이 발생합니다.

fun main() = runBlocking {
    try {
        withTimeout(1000) {
            delay(2000)
        }
    } catch (e: TimeoutCancellationException) {
        println("Timeout 발생!")
    }
}

// Timeout 발생!

위 코드에서 withTimeout(1000) 블록 내의 작업이 1초 내에 완료되지 않으면 TimeoutCancellationException이 발생하고 "Timeout 발생!"이 출력됩니다.

withTimeout 함수는 정확히는 코루틴을 취소합니다. delay() 함수는 suspend 함수이므로, withTimeout 블록 내에서 호출될 때도 정상적으로 동작합니다. withTimeout 블록 내에서 delay()를 호출하면, delay()가 지정된 시간 동안 코루틴을 일시 중단시키고, 이 시간 내에 withTimeout의 제한 시간이 경과하면 예외가 발생하여 코루틴이 취소됩니다.

 

withTimeout 함수와 non-suspend 함수

private fun nonSuspendFunction() {
    Thread.sleep(2000)
}

fun main() = runBlocking {
    try {
        withTimeout(1000) {
            nonSuspendFunction()
            println("타임아웃이 발생하면 이 줄은 출력되지 않습니다.")

        }
    } catch (e: TimeoutCancellationException) {
        println("Timeout 발생.")
    }
}

// 타임아웃이 발생하면 이 줄은 출력되지 않습니다.

위 예제에서 "타임아웃이 발생하면 이 줄은 출력되지 않습니다." 메시지가 출력됩니다. withTimeout 블록 내에서 suspend 함수가 아닌 non-suspend 함수를 호출하면 withTimeout은 기대와 다르게 동작합니다.

withTimeout은 주어진 시간 내에 코루틴이 완료되지 않으면 TimeoutCancellationException을 발생시키도록 설정되어 있습니다. 하지만, non-suspend 함수는 코루틴을 일시 중지하지 않습니다. 특히 Thread.sleep 메서드는 2초 동안 코루틴이 아닌 스레드를 차단하기 때문에, withTimeout의 1초 제한이 무시되고, "Timeout 발생." 메시지가 출력되지 않습니다. 따라서 코루틴이 suspend 상태에 들어가지 않아서 withTimeout이 시간 초과를 감지하고 예외를 발생시킬 기회를 가지지 못합니다.

fun main() = runBlocking {
    try {
        withTimeout(1000) {
            feignClient.getData() // API를 호출하여 데이터를 가져옵니다.
        }
    } catch (e: TimeoutCancellationException) {
        println("타임아웃 발생.")
    }
}

만약 위 처럼 OpenFeign FeignClient로 외부 API를 호출하여 데이터를 가져올 때 호출시간이 1초가 넘어가는 경우에도 withTimeout은 작동하지 않습니다.

OpenFeign은 기본적으로 동기식 네트워크 호출을 수행합니다. 이는 네트워크 요청이 완료될 때까지 현재 스레드가 차단된다는 의미입니다. 따라서 API 호출 중 withTimeout의 제한 시간이 초과되어도 예외를 발생시키지 않습니다. 이런 경우 FeignClient를 비동기적 방식으로 작동하도록 설정하거나 OpenFeign 자체적으로 지원하는 타입아웃 설정을 추천드립니다.

https://github.com/OpenFeign/feign/pull/1706

 

Support kotlin coroutines by wplong11 · Pull Request #1706 · OpenFeign/feign

Resolves: #1565 !! This is working PoC Inspired by PlaytikaOSS/feign-reactive#486 TODO Separate Kotlin support module Enhance test case Refactoring Clean up pom.xml

github.com

openFeign 버전 12부터 suspend를 지원하니 참고하시기 바랍니다.

 

결론

  • withTimeout 내에서 delay 같은 suspend 함수를 사용할 경우, 지정된 시간 내에 블록이 완료되지 않았을 때 정상적으로 TimeoutCancellationException이 발생합니다.
  • withTimeout 내에서 non-suspend 함수를 사용할 경우, 해당 함수가 스레드를 차단할 수 있어 withTimeout의 시간 제한이 예상대로 작동하지 않습니다.
  • 따라서 withTimeout 블록 내에서는 suspend 함수를 사용하여 코루틴 중단 지점을 생성하는 것이 중요합니다. 이렇게 해야 withTimeout의 시간 제한이 올바르게 적용되어 원하는 결과를 얻을 수 있습니다.