inflearn logo
강의

Course

Instructor

Complete Kotlin Coroutines

KTOR Server 에서 delay

Resolved

136

kokoxg2

6 asked

0

- 학습 관련 질문을 남겨주세요. 질문을 상세히 작성하면 더 좋습니다.
- 서로 예의를 지키며 존중하는 문화를 만들어가요.
- 잠깐! 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요.

 

 

안녕하세요, coroutine 강의를 듣고 ktor server 공부를 하는 중에 delay 관련 질의가 생겨 글 남깁니다.

 

delay 의 경우 coroutine 이 스레드를 양보하고, 일정 시간 후에 다시 스레드가 비어있으면 점유하는 형식으로 진행될텐데, 아래와 같은 경우 delay 이후 코드가 실행되지 않습니다.

 

사용자 → KTOR Server → suspend function call → loop { logic → 너무 많은 동작을 제한하기위한 delay }

 

ktor 는 기본적으로 요청이 IO Dispatcher 를 사용하는 것 같은데, coroutine 에서 delay 이후 기능이 동작하지 않는 것에 대한 이유가 있을까요..? (강의랑 무관한 내용이라 죄송합니다 ㅠ.ㅠ)

 

/* Routing.kt */
fun Application.configureRouting(searcher: BaseSearcher) {
    routing {

        post("/search") {
            val params = call.receive<Map<String, String>>()

            searcher.logging("Received POST request with params: $params")
            val result: List<BaseDTO> = searcher.search(params)
            searcher.logging("POST Result: $result")
            call.respond(HttpStatusCode.OK, mapOf("result" to result))
        }

        get("/") {
            call.respondText("Hello World!")
        }
    }
}

/* Searcher.kt */


    /**
     *  함수 이름 : search
     *  내용 설명 : 입력된 map<string, string> param 을 바탕으로 요청 전송 및 응답 반환
     */
    override suspend fun search(searchParam: Map<String, String>): List<SimpleSearchLandResultDTO> {
        // 별도의 Job 으로 분리해서 오류시 상위 전파 제한
        return HttpClient(CIO).use { client ->
            val parseResultLst: MutableList<SimpleSearchLandResultDTO> = mutableListOf()

            try {
                val initRequestResultDto: SimpleSearchResultDTO =
                    sendFormedRequest(client, SearcherConst.URL_MAIN_PAGE, false)

                // 기본 페이지 요청이 성공했을 때 다음 요청을 진행
                if (initRequestResultDto.isSuccess) {
                    val mutableSearchParam = searchParam.toMutableMap()

                    delay(300)

                    while (true) {

                        val eachPageSearchResultDto: SimpleSearchResultDTO =
                            sendFormedRequest(client, makeGetUrl(mutableSearchParam), true)

                        // 실패한 경우 종료
                        if (!eachPageSearchResultDto.isSuccess) {
                            logging("[search] 조회 중 오류가 발생하였습니다.")
                            break
                        }
                        // 결과가 빈 경우도 종료
                        else if (eachPageSearchResultDto.landSearchResultLst.isEmpty()) {
                            break
                        }

                        // 결과가 있는 경우엔 전부 더해줌
                        parseResultLst.addAll(eachPageSearchResultDto.landSearchResultLst)

                        logging("[search] result(${eachPageSearchResultDto.landSearchResultLst[0].currentPage} : ${eachPageSearchResultDto.landSearchResultLst}")

                        // 마지막 페이지인 경우 종료
                        if (eachPageSearchResultDto.landSearchResultLst[0].currentPage ==
                            eachPageSearchResultDto.landSearchResultLst[0].maxPage
                        ) {
                            break
                        } else {
                            // 다음 페이지 수집 진행
                            mutableSearchParam["currentPage"] =
                                (eachPageSearchResultDto.landSearchResultLst[0].currentPage + 1).toString()

                            delay(300)
                        }
                    }
                }
            } catch (e: Exception) {
                logging("[search] search 함수 중 오류가 발생하였습니다. param: $searchParam, exception: $e")
            }

            logging("[search] Result : $parseResultLst")

            // parseResultLst 반환
            parseResultLst
        }
    }

private suspend fun sendFormedRequest(client: HttpClient, url: String, isResultNeed: Boolean): SimpleSearchResultDTO {
        return try {

            val response = client.get(url) {
                headers {
                    append(HttpHeaders.Cookie, cookie.toCookieString())
                    append(HttpHeaders.Accept, SearcherConst.DEFAULT_HTTP_ACCEPT)
                    append(HttpHeaders.AcceptEncoding, SearcherConst.DEFAULT_HTTP_ACCEPT_ENCODING)
                    append(HttpHeaders.Connection, SearcherConst.DEFAULT_HTTP_CONNECTION)
                    append(HttpHeaders.AcceptLanguage, SearcherConst.DEFAULT_HTTP_ACCEPT_LANGUAGE)
                    append(HttpHeaders.UserAgent, SearcherConst.DEFAULT_HTTP_USER_AGENT)
                }
            }

            cookie.putAll(response.headers[HttpHeaders.SetCookie]?.split(";")?.map { it.split("=") }?.
            filter { it.size == 2 }?.filter { it[1] != "/" && it[1].isNotEmpty() }?.associate { it[0] to it[1] } ?: mapOf())

            if (response.status != HttpStatusCode.OK) {
                SimpleSearchResultDTO(false)
            } else {
                SimpleSearchResultDTO(true, if(isResultNeed) parseData(response.bodyAsText()) else mutableListOf())
            }
        } catch(e: Exception) {
            logging("[sendFormedRequest] Error : $e", Level.ERROR)
            SimpleSearchResultDTO(false)
        }
    }

 

 


 

 

android kotlin coroutine kotlinx.coroutines android-coroutine

Answer 2

0

seyoungcho2

kokoxg2님 안녕하세요. 지식 공유자 조세영입니다.

제가 ktor를 실제로 사용해본 적이 없어 코드 흐름만 보고 답변 드립니다.

질문 주신 코드만으로는 delay 이후 코드의 동작을 제한하는 부분이 없는 것 같아 보이는데요. delay 이후 코드가 동작하지 않는 다는건 너무 다양한 문제가 있기 때문에(모든 스레드 블로킹, 예외 발생으로 취소돼 동작하지 않는 것처럼 보이는 것, delay 재개 이후 코드에서 일시중단 후 재개 하지 않음, delay 도중 코루틴이취소됨 등) 정확한 부분은 디버깅을 해봐야 알 수 있을 것 같습니다.

도움이 되었으면 좋을 것 같습니다. 감사합니다.

0

communityai8509

안녕하세요, 인프런 AI 인턴입니다. kokoxg2님.

Ktor 서버 환경에서 delay 함수 사용 후 실행되지 않는 현상에 대해 질문 주셔서 감사합니다. delay 함수는 코루틴이 특정 시간 동안 대기하도록 하지만 스레드를 점유하지 않으며 다른 코루틴이 실행될 수 있도록 합니다. 그러나, IO Dispatcher와 같은 환경에서는 여러 가지 이유로 인해 기대한 대로 작동하지 않을 수 있습니다. 가능한 몇 가지 원인을 요약하여 말씀드리겠습니다.

가능한 원인 및 해결책

  1. 스레드 제한 문제:

    • Dispatcher.IO는 기본적으로 최대 64개의 코루틴을 위한 스레드를 제공합니다. 스레드 수가 많아지면 스레드가 점유되지 않거나, 이미 많은 다른 작업으로 인해 delay 후 대기 중일 수 있습니다. 이 경우,
      • 사용 가능한 스레드 수가 충분한지 확인하세요.
      • 필요에 따라 Dispachers.IO 대신 별도의 스레드를 만들어 사용해보세요.
  2. 코루틴 범위 및 실행 문제:

    • 호출 방식이나 범위에 따른 문제가 있을 수 있습니다. 코루틴이 올바른 디스패처에서 실행되고 있는지 확인하세요.
    • search 함수 내에서 launch 등의 코루틴 빌더를 사용하여 실행 컨텍스트 문제를 해결해 보세요.
  3. 중복처리 문제:

    • 주어진 코드에서 불필요한 중복이나 복잡한 논리로 인해 특정 코드가 예상보다 일찍 중단되거나 실행되지 않을 수 있습니다. 로그를 통해 어떤 지점에서 코드가 중단되는지 정확히 확인해 보세요.
  4. Exception Handling:

    • try-catch 구문으로 감싸져 있어 오류 시 작성한 예외 처리가 오는 경우 이어서 실행되지 않을 수 있습니다. 예상치 못한 곳에서 예외가 발생하진 않는지 확인하세요.
  5. HTTP 클라이언트의 사용 문제:

    • HttpClient를 사용할 때, 비동기 호출함에 있어 사용한 API가 실제로 논블로킹 방식을 지원하는지 확인이 필요합니다.

조언 및 제안

  • 발생한 구체적인 현상을 로그를 통해 더 자세히 파악하세요.
  • 가능한 스레드 문제를 해소하기 위해 제한을 확장하거나 적절한 Dispatcher를 사용해 보세요.
  • 필요한 경우, 코루틴의 작동을 시각화하여 문제 해결에 도움을 받을 수 있습니다.

관련된 내용에 대한 이해를 더 돕기 위해 코루틴 공식 문서를 참조해 보실 것을 추천드립니다.

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다. 현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏 추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.

CoroutineDispatcher에 대한 질문

0

79

2

코루틴과 JVM이 끝나는 조건에 대해 문의 드립니다.

0

83

2

Job() 생성후 complete을 불러 주어야 하는 경우가 헷깔립니다.

1

66

1

왜 runBlocking 종료로 메인스레드가 종료될까요?

0

120

1

cancel 질문드립니다.

1

85

2

delay 함수 사용 시 스레드 양보

1

127

2

강사님께서 번역해주신 코틀린 코루틴 공식 가이드 문서 문의

2

150

2

Flow와 Channel

1

146

2

[코틀린 코루틴의 정석 책 추첨 이벤트] 강의 구매 기간 관련 문의

1

221

1

[코루틴 테스트 심화] runTest의 스레드 관련 문의

1

164

2

job과 코루틴의 관계?

3

258

2

코루틴의 blocking I/O작업 처리

0

161

2

CoroutineDispatcher(Default, IO)의 limitedParallelism 관련 질문

1

242

1

suspend 문의 드려요

2

131

2

스레드 양보 예제 + 코루틴/멀티스레드 사용 예시 질문

1

184

2

coroutineScope 관련 질문 및 실제 사용 사례에 대한 질문

1

258

3

Coroutine 취소 시점 체크

1

139

2

spring web mvc 환경에서 coroutine을 사용해보신 경험이 있으신지 궁금합니다.

1

326

2

코루틴이 멀티스레드의 단점을 해결했다는 부분에 대해 질문드립니다.

2

182

1

Dispatcher.IO의 동작원리

0

254

3

실무에서 runBlocking 와 CoroutineScope 실무 사용에 대해

0

238

2

Code3-6에서 imageProcessingDispatcher가 inline으로는 동작하지 않는 이유가 궁금합니다

1

121

2

공유 스레드 풀 질문드려요!

1

158

2

코루틴 이름 출력관련해서 질문이 있습니다!

2

160

1