Sometimes we want to have a suspend function that calls other suspend functions inside of a transaction:
suspend fun mySuspendFunction(jooqContext: DSLContext) {
jooqContext.transaction { config ->
val transactionContext: DSLContext = config.dsl()
// ... some code that uses transactionContext ...
anotherSuspendFunction(transactionContext)
// ... more code that uses transactionContext ...
}
}
However, this won't compile, because jOOQ's DSLContext.transaction takes a non-suspending function as its parameter, and so the body of the transaction cannot call a suspend function directly. The above will fail to compile with an error like:
Suspension functions can be called only within coroutine body
One workaround is to use runBlocking around the suspend calls:
suspend fun mySuspendFunction(jooqContext: DSLContext) {
jooqContext.transaction { config ->
val transactionContext: DSLContext = config.dsl()
// ... some code that uses transactionContext ...
runBlocking {
anotherSuspendFunction(transactionContext)
}
// ... more code that uses transactionContext ...
}
}
However the documentation for runBlocking advises against using it in this way ("designed to bridge regular blocking code to libraries that are written in suspending style, to be used in main functions and in tests").
That's just a hint of the real problem, which is that because DSLContext.transaction is calling a non-suspend function for the transaction body, the coroutine's thread is blocking (ie: cannot suspend) for the duration of the transaction.
I thought about making a suspend-friendly transaction wrapper for Kotlin,
but jOOQ does not yet have a procedural transaction API. Also, DSLContext.connection blocks on the supplied function so I don't see a way to get a hold of the single Connection for the duration of the transaction without blocking.
Is there a way to use a jOOQ transaction in a Kotlin coroutine that does not block for the duration of the transaction, and which allows suspend functions to be called from within the transaction body?