is there any upcoming documentation/updates regard...
# exposed
m
is there any upcoming documentation/updates regarding suspending transactions? I am running into weird issues with nested transactions and closed connections and I had to come up with a fix for scenarios like this:
Copy code
newSuspendedTransaction {
   insert(1)
   newSuspendedTransaction {
      insert(2)
   }
   throw Error("failure")
 }
so
insert(2)
is rolled back (otherwise it commits) My attempt to fix it is the following piece of code:
Copy code
suspend fun <T> suspendedTransaction(
    context: CoroutineContext? = null,
    db: Database? = null,
    transactionIsolation: Int? = null,
    statement: suspend Transaction.() -> T,
): T {
    val existing = TransactionManager.currentOrNull()
    return if (existing == null || existing.connection.isClosed) {
        // Create a new (and single) suspended transaction that will be propagated as ContextElement internally
        newSuspendedTransaction(
            context = context,
            db = db,
            transactionIsolation = transactionIsolation,
            statement = statement
        )
    } else {
        existing.statement()
    }
}
Also, sometimes (and for no apparent reason), a suspended transaction gets a closed connection as soon as it starts. https://github.com/JetBrains/Exposed/issues/910 That issue seemed to try to solve the bug but I am pretty sure it's still happening. I am only using suspended transactions in code. I might be unaware of some internals that might cause even more issues down the line doing by simply doing
existing.statement()
so if any maintainers can take a look it would be nice. Note the
|| existing.connection.isClosed
to mitigate the broken connection transactions.
a
You should use
withSuspendTransaction
if you want
insert(2)
to rollback on failure. Creating a helper like this isn’t a bad idea though. The closed connection issue sounds strange and not something I’ve ran into during 4-ish years of Exposed. The issue you linked might not be related either, as it doesn’t seem to concern suspended transactions exclusively. Do you use a connection pool and have you verified it’s not something happening on that end? Any stack traces?
Nesting
newSuspendedTransaction
creates independent transactions, hence a failure in the inner transaction would not roll back the outer either. This is mentioned somewhere on the wiki page, but it doesn’t explicitly explain the purpose of
withSuspendTransaction
IIRC.
m
I used the combination of
newSuspendedTransaction
and
withSuspendedTransaction
initially but it seemed redundant, will put it back just in case there is some edge case in the
withSuspendedTransaction
implementation I missed instead of just calling the block like I am doing. I also wanted a clean and decoupled api so it looks like the blocking version and not two different flows
Copy code
transaction {
   ...
   transaction { 
   }
}
that same API with coroutines assumes your code knows it's in a nested transaction to make it work correctly
Copy code
newSuspendedTransaction {
...
   otherService.method() <- is it newSuspended or withSuspended? the other service shouldn't have to know
}
so the helper was actually required. I think it's a pretty common case for composing use cases / service operations.
a
Yup, I agree and that’s why I said it’s not a bad idea 😅 🤝 I have something similar that passes
statement
to
withSuspendTransaction
, though I mostly just pass
Transaction
around as a context nowadays. This is from the wiki, btw:
Note: newSuspendedTransaction and suspendedTransactionAsync are always executed in a new transaction to prevent concurrency issues when query execution order could be changed by the CoroutineDispatcher.
👀 1
170 Views