Hello, I have question about *Schedulers* and *cor...
# spring
d
Hello, I have question about Schedulers and coroutines together in Spring application. I am basically having this code which defines scheduled method, with always throwing RuntimeException (for testing purposes)
Copy code
private val coroutineScope = CoroutineScope(Dispatchers.Unconfined)

private val logger: Logger = LoggerFactory.getLogger("BirthdayCongratulationScheduler::class.java")

/**
 * Set up cron which starts every 10 seconds
 */
@Scheduled(cron = CRON_SCHEDULE)
@SchedulerLock(name = CRON_NAME)
fun congratulate() {
    val handler = CoroutineExceptionHandler { _, exception ->
        logger.error("CoroutineExceptionHandler got $exception")
    }
    runCatching {
        logger.info("CRON $CRON_NAME has started.")
        coroutineScope.launch(handler) {
            throw RuntimeException("Not implemented")
        }
    }.onFailure { throwable ->
        logger.error("CRON $CRON_NAME has failed. Check the error message: ${throwable.message}.")
    }.onSuccess {
        logger.info("CRON $CRON_NAME has finished.")
    }
}

companion object {
    private const val CRON_NAME = "myScheduler"
    private const val CRON_SCHEDULE = "*/10 * * * * *"
}
and this is print log from console:
Copy code
Started ApiApplicationKt in 8.411 seconds (process running for 8.76)
2023-10-20T10:22:10.103+02:00  INFO 82566 --- [   scheduling-1] .h.e.s.s.BirthdayCongratulationScheduler : CRON birthdayScheduler has started.
2023-10-20T10:22:10.106+02:00 ERROR 82566 --- [   scheduling-1] .h.e.s.s.BirthdayCongratulationScheduler : CoroutineExceptionHandler got java.lang.RuntimeException: Not implemented
2023-10-20T10:22:10.106+02:00  INFO 82566 --- [   scheduling-1] .h.e.s.s.BirthdayCongratulationScheduler : CRON birthdayScheduler has finished.
2023-10-20T10:22:20.032+02:00  INFO 82566 --- [   scheduling-1] .h.e.s.s.BirthdayCongratulationScheduler : CRON birthdayScheduler has started.
2023-10-20T10:22:20.034+02:00  INFO 82566 --- [   scheduling-1] .h.e.s.s.BirthdayCongratulationScheduler : CRON birthdayScheduler has finished.
2023-10-20T10:22:30.033+02:00  INFO 82566 --- [   scheduling-1] .h.e.s.s.BirthdayCongratulationScheduler : CRON birthdayScheduler has started.
2023-10-20T10:22:30.033+02:00  INFO 82566 --- [   scheduling-1] .h.e.s.s.BirthdayCongratulationScheduler : CRON birthdayScheduler has finished.
repeating infinitely...
I am really confused, why
CoroutineExceptionHandler
is catching the exception only one time. And after that the cron is still running without calling the logic in
runCatching{}
as you can see in print in console? Can someone explain it to me? Or just provide link where I can read about this? Is this approach even correct? Or what is the best practise to call suspend functions in body of Scheduled method? thanks, Dusan
s
When you call
launch
, it returns immediately, and launches the new coroutine as a separate control flow. That means you can't catch errors from the launched job by surrounding the
launch
invocation with `try`/`catch` or
runCatching
. Errors that happen in the new coroutine will be handled by the coroutine's scope instead. That's why your code is logging the onSuccess message instead of the onFailure message. A coroutine scope contains a
Job
which tracks the success or failure of its child coroutines. By default, a failure (that is, an unhandled exception) in any of the scope's jobs will cause the whole scope to fail. Adding a
CoroutineExceptionHandler
doesn't change that behaviour. Per the docs, the exception handler is invoked only after the scope has been terminated by an unhandled error. That's why your coroutine is only running once. On subsequent invocations of
launch
, the scope is already cancelled and so the new coroutine won't be started. One possible solution would be to move the error handling inside the
launch
block. Alternatively, you replace
launch
entirely with
runBlocking
, if you're happy with the scheduler thread being blocked while the coroutine runs. Mixing coroutines with Spring scheduling is a tricky problem and I'm not aware of a perfect solution right now. Coroutines do include their own scheduling capabilities—just make a loop with a `delay`—which might be a simpler solution if you just need a periodically repeating task that calls a suspending function.
d
Thank you Sam, now it’s clear to me. I should be happy to go with
runBlocking{}
, cause only threads for schedulers would be affected and blocked and other parts (threads) of app will be still running seamlessly.