인프런 커뮤니티 질문&답변

kokoxg2님의 프로필 이미지
kokoxg2

작성한 질문수

코틀린 코루틴 완전 정복

KTOR Server 에서 delay

해결된 질문

작성

·

20

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)
        }
    }

 

 


 

 

답변 2

0

조세영님의 프로필 이미지
조세영
지식공유자

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

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

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

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

0

안녕하세요, 인프런 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를 사용해 보세요.
  • 필요한 경우, 코루틴의 작동을 시각화하여 문제 해결에 도움을 받을 수 있습니다.

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

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

kokoxg2님의 프로필 이미지
kokoxg2

작성한 질문수

질문하기