-
Kotlin coroutine graceful shutdown in spring bootSpring Framework 2025. 4. 6. 13:31반응형
graceful shutdown
graceful shutdown은 애플리케이션이 종료될 때, 현재 진행 중인 작업을 완료한 후 종료하는 것을 말합니다.
couroutine과 graceful shutdown
@Component class CoroutineGracefulShutdown() : ApplicationRunner { override fun run(args: ApplicationArguments) { runBlocking { launch(Dispatchers.IO) { logger.info { "start" } sleep(30000) // 30초 기다림 logger.info { "end" } } } } }
만약 위와 같은 코드를 실행했다가 intellij의 Stop 버튼을 눌러서 SIGINT로 애플리케이션의 종료를 수행하면 어떻게 될까요?
start 요청이 보이고 30초를 기다리지 않고 애플리케이션이 종료되어 end 로그가 남지 않습니다.
만약 해당 coroutine이 애플리케이션에서 데이터베이스에 적재하는 등 중요한 역할을 수행하고 있다면 데이터 정합성이 맞지 않게 되고, 배포과정에서 간헐적으로 발생하기 때문에 이 원인을 찾기 어려울 수 있습니다.
Spring이 지원해 주는 option
server: shutdown: graceful // 기본 값은 immediate
위 옵션을 적용하더라도 ApplicationRunner의 경우에는 Tomcat의 요청을 받고 connection이 수립되지 않기 때문에 단순하게 위 옵션으로는 coroutine의 graceful shtudown이 지원되지 않습니다.
coroutine의 CoroutineDispatcher
CoroutineDispatcher는 만들어진 코루틴을 스레드로 보내는 역할을 수행하는 객체입니다.
launch(Dispatchers.IO) // << CoroutineDispatcher
launch에서는 CoroutineContext를 인자로 받아 넘기게 되는데 이때 CoroutineDispatcher도 CoroutineContext를 구현하고 있습니다.
위 코드에서는 Dispatchers.IO가 코루틴을 쓰레드로 보내는 역할을 수행하게 됩니다.
ThreadPool의 graceful shutdown
kotlin의 coroutine도 결국에는 쓰레드를 점유해서 수행되다 보니 수행되는 스레드가 graceful shutdown 된다면 coroutine도 자연스럽게 graceful shutdown이 지원될 수 있을 것 같습니다.
Spring에서 ThreadPool의 graceful shutdown을 지원하기 위해서는 아래와 같이 ThreadPool을 선언할 수 있습니다.
@Component class MyThreadPool{ @Bean fun myExecutor(): ThreadPoolTaskExecutor{ val executor = ThreadPoolTaskExecutor() executor.corePoolSize = 10 executor.maxPoolSize = 10 executor.queueCapacity = 0 executor.setThreadNamePrefix("test-task-") executor.setWaitForTasksToCompleteOnShutdown(true) executor.setAwaitTerminationSeconds(40) executor.initialize() return executor } }
위 코드는 40초의 유예시간을 주어 thread pool 내의 task가 종료될 수 있는 시간을 줍니다.
단, ThreadPoolTaskExecutor를 Spring Bean으로 등록하지 않는다면 graceful shutdown이 지원되지 않습니다.
Executor와 CoroutineDispatcher
하지만 ThreadPoolTaskExecutor는 Executor 인터페이스를 구현하고 있으며, launch에 인자로 넘기기 위해서는 CoroutineDispatcher 객체를 넘겨야 합니다.
kotlin coroutine 라이브러리에는 아래 코드와 같이 Executor를 CoroutineDispatcher로 변환하는 확장함수가 제공되는데 이를 활용해 볼 수 있습니다.
Executor.asCoroutineDispatcher()
최종 코드
@Component class CoroutineGracefulShutdown( private val myExecutor: ThreadPoolTaskExecutor, ) : ApplicationRunner { override fun run(args: ApplicationArguments?) { val coroutineDispatcher = myExecutor.asCoroutineDispatcher() runBlocking { launch(coroutineDispatcher) { logger.info { "start" } sleep(30000) // 30초 기다림 logger.info { "end" } } } } }
graceful shutdown이 지원되도록 옵션을 설정한 ThreadPoolTaskExecutor 객체를 주입받아서 CoroutineDispatcher로 변환한 후 넘겨주게 되면 intellij Stop으로 SIGINT 요청을 날리면 30초를 기다리고 end 로그까지 출력되는 모습을 확인할 수 있습니다.
참고자료
Spring Boot Graceful shtudown 동작과정
Spring Boot Graceful shutdown 동작과정
개요 Spring Boot Application에서 Controller가 요청을 처리하고 응답이 되지 않았는데 종료요청이 도달하면 어떻게 될까요? Client는 응답을 받지 못하고 timeout이 발생합니다. Spring Boot 2.3에서 제공하는 gr
junuuu.tistory.com
[Coroutine] 3. CoroutineDispatcher 란 무엇인가?
Coroutine을 공부하면서 CoroutineDispatcher에 대해 상세히 설명된 글이 없어서 이 글을 작성하게 되었다. 많은 사람들에게 도움이 되길 바란다. CoroutineDispatcher 란 무엇인가? 코루틴을 시작하게 되면, Co
kotlinworld.com
ThreadPoolTaskExecutor란? (ThreadPoolTaskExecutor vs ThreadPoolExecutor)
ThreadPoolTaskExecutor란? public class ThreadPoolTaskExecutor extends ExecutorConfigurationSupport implements AsyncListenableTaskExecutor, SchedulingTaskExecutor { private final Object poolSizeMonitor = new Object(); private int corePoolSize = 1; private
junuuu.tistory.com
'Spring Framework' 카테고리의 다른 글
MSA 환경에서 enum에 대한 위험성 줄여나가기 (0) 2024.11.16 Spring Bean 이름은 왜 소문자로 시작할까? (0) 2024.11.03 분산시스템에서 로깅 트레이싱 전파는 어떻게 이루어질까? (0) 2024.10.26 Spring Boot Distributed Scheduling (0) 2024.06.30 프로젝트에 Feature Flag 적용하기 (0) 2024.06.01