I'm working on a big Quarkus non-reactive project. The migration as a reactive one is off the table for now.
I have different long running tasks to start from API calls, tasks that could be cancelled anytime on-demand. So I developped a small library starting a block of code (provided by the library caller) inside a coroutine. A reference is kept and returned to the API response for later potential cancellation.
The content of my task is to fetch a bunch of data from a DB and publish it to Kafka. The task method is annotated with @ActivateRequestContext
to prevent having the exception error
javax.enterprise.context.ContextNotActiveException: Cannot use the EntityManager/Session because neither a transaction nor a CDI request context is active. Consider adding @Transactional to your method to automatically activate a transaction, or @ActivateRequestContext if you have valid reasons not to use transactions.
Now, I would like to insert delays while processing for some reasons, and even without delays, i need to use yield
and have the code suspendable to be cancellable.
Doing so, the exception reappears after the first delay
or yield
.
I globally understand that the coroutine being suspended, its context is lost when reactivated. The following snippet shows how i work around the issue by using a sub-method, non suspendable, with its own @ActivateRequestContext
annotation.
@Path("/admin/jobs")
@Produces(MediaType.APPLICATION_JSON)
class JobsXP {
@Inject
@field: Default
lateinit var logger: Logger
@POST
fun jobLauncher(): String {
val someRefForLaterCancellation = GlobalScope.launch(Dispatchers.IO) {
try {
jobTask()
} catch (ce: CancellationException) {
// ...
} catch (e: Exception) {
// ...
}
}
return "job started..."
}
// This annotation is needed for the first MyEntity.findById() but not enough for the 2nd one
@ActivateRequestContext
suspend fun jobTask() {
val ids = listOf(1L, 2, 3, 4)
ids.forEach { id ->
try {
// version NOT OK
val myEntity = MyEntity.findById(id)
// ...work work...
logger.info(myEntity)
// version OK
// processing(id)
} catch (e: Exception) {
logger.error(e)
}
delay(100)
}
}
@ActivateRequestContext
fun processing(id:Long) {
val myEntity = MyEntity.findById(id)
// ...work work...
logger.info(myEntity)
}
}
Now i'm not totally satisfied with this workaround. It implies that the hibernate calls must be perfectly splittable into independant subunits of work, which might not always be the case, for performance reasons or complex algorithms handling many entities.
I'm aware the io.quarkus:quarkus-smallrye-context-propagation
dependency seems to exist for this kind of issue but i don't think it's applicable in my non-reactive project.
So basically, i'm doing it right ? Are kotlin coroutines not really compatible with a Quarkus project (or the other way round..) ?
Thanks for your advices. P.