Using blocking transactions after a suspending transaction causes a "Connection is closed" exception
See original GitHub issueI’m not sure if it is intentional, I tried searching about it on the Wiki, FAQ and in the issues and I couldn’t find anything about it. The only similar issues that I’ve found was #680 and #601
Of course, you shouldn’t mix blocking and suspending methods, after all, why would you do that? The reason I found out about it is that I only recently found out that Exposed does have coroutine-aware methods (whoops) and I started migrating away from the blocking transactions to the suspended transactions, so I changed a few of my transactions to suspended transactions… then I found out that you can’t mix them, oops.
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.jetbrains.exposed.dao.id.LongIdTable
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.SchemaUtils
import org.jetbrains.exposed.sql.selectAll
import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction
import org.jetbrains.exposed.sql.transactions.transaction
import kotlin.concurrent.thread
fun main() {
val database = Database.connect("jdbc:postgresql://127.0.0.1:5432/teste", driver = "org.postgresql.Driver",
user = "postgres", password = "postgres")
transaction(database) {
SchemaUtils.createMissingTablesAndColumns(
OriginalTable
)
}
GlobalScope.launch {
println("Starting...")
val transaction1 = newSuspendedTransaction(Dispatchers.IO, database) {
OriginalTable.selectAll()
.toList()
}
println("Coroutine finished")
val transaction2 = transaction(database) {
OriginalTable.selectAll()
.toList()
}
println("Done!")
}
thread {
while (true) {
Thread.sleep(1_000)
}
}
}
object OriginalTable : LongIdTable() {
val testNumber = integer("test_number")
}
10:32:30.445 [main] INFO Exposed - Preparing create tables statements took 403ms
10:32:30.463 [main] INFO Exposed - Executing create tables statements took 14ms
10:32:30.609 [main] INFO Exposed - Extracting table columns took 146ms
10:32:31.210 [main] INFO Exposed - Extracting column constraints took 600ms
10:32:31.210 [main] INFO Exposed - Preparing alter table statements took 747ms
10:32:31.211 [main] INFO Exposed - Executing alter table statements took 1ms
10:32:31.229 [main] INFO Exposed - Checking mapping consistence took 18ms
Starting...
10:32:31.386 [DefaultDispatcher-worker-1] DEBUG Exposed - SELECT original.id, original.test_number FROM original
Coroutine finished
Exception in thread "DefaultDispatcher-worker-2" org.jetbrains.exposed.exceptions.ExposedSQLException: org.postgresql.util.PSQLException: This connection has been closed.
SQL: [SELECT original.id, original.test_number FROM original]
at org.jetbrains.exposed.sql.statements.Statement.executeIn$exposed_core(Statement.kt:50)
at org.jetbrains.exposed.sql.Transaction.exec(Transaction.kt:126)
at org.jetbrains.exposed.sql.Transaction.exec(Transaction.kt:112)
at org.jetbrains.exposed.sql.Query.iterator(Query.kt:212)
at kotlin.collections.CollectionsKt___CollectionsKt.toCollection(_Collections.kt:1200)
at kotlin.collections.CollectionsKt___CollectionsKt.toMutableList(_Collections.kt:1233)
at kotlin.collections.CollectionsKt___CollectionsKt.toList(_Collections.kt:1224)
at com.mrpowergamerbr.loritta.listeners.TestKt$main$2$transaction2$1.invoke(Test.kt:36)
at com.mrpowergamerbr.loritta.listeners.TestKt$main$2$transaction2$1.invoke(Test.kt)
at org.jetbrains.exposed.sql.transactions.ThreadLocalTransactionManagerKt$transaction$1.invoke(ThreadLocalTransactionManager.kt:121)
at org.jetbrains.exposed.sql.transactions.ThreadLocalTransactionManagerKt.keepAndRestoreTransactionRefAfterRun(ThreadLocalTransactionManager.kt:212)
at org.jetbrains.exposed.sql.transactions.ThreadLocalTransactionManagerKt.transaction(ThreadLocalTransactionManager.kt:113)
at org.jetbrains.exposed.sql.transactions.ThreadLocalTransactionManagerKt.transaction(ThreadLocalTransactionManager.kt:111)
at com.mrpowergamerbr.loritta.listeners.TestKt$main$2.invokeSuspend(Test.kt:34)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
Caused by: org.postgresql.util.PSQLException: This connection has been closed.
at org.postgresql.jdbc.PgConnection.checkClosed(PgConnection.java:857)
at org.postgresql.jdbc.PgConnection.prepareStatement(PgConnection.java:1763)
at org.postgresql.jdbc.PgConnection.prepareStatement(PgConnection.java:410)
at org.postgresql.jdbc.PgConnection.prepareStatement(PgConnection.java:1777)
at org.jetbrains.exposed.sql.statements.jdbc.JdbcConnectionImpl.prepareStatement(JdbcConnectionImpl.kt:58)
at org.jetbrains.exposed.sql.statements.Statement.prepared(Statement.kt:25)
at org.jetbrains.exposed.sql.statements.Statement.executeIn$exposed_core(Statement.kt:48)
... 19 more
The code works fine if:
- They are both blocking transactions
- They are both coroutine-aware transactions
Issue Analytics
- State:
- Created 3 years ago
- Comments:7 (5 by maintainers)
Top Results From Across the Web
Understand and resolve SQL Server blocking problems
Find the query and transaction that is causing the blocking (what is holding locks for a prolonged period)
Read more >Handling java.sql.SQLRecoverableException: Closed ...
This error message indicates that the connection used by the backup task (or any other long-running operation that relies on a single database...
Read more >JDBC distributed transactions - IBM
When you end a transaction, all open ResultSets that were created under that transaction automatically close. It is recommended that you explicitly close...
Read more >what does suspending a transaction means? - Stack Overflow
When a transaction is suspended , it waits until it can pick up where it left off. This means, the changes that happened...
Read more >Spring Transaction Management: @Transactional In-Depth
The proxy has access to a transaction manager and will ask it to open and close transactions / connections. The transaction manager itself...
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
This seems to happen when a coroutine shares the same thread as another thread that had an connection before.
If you create your own coroutine dispatcher based on a fixed thread pool of 16 threads, this bug happens only sometimes.
If you change your thread pool to only 1 thread, this bug happens every time.
What looks like what it is happening is that the transaction context is leaking to the original coroutine. (the
GlobalScope.launch {
part), if you create a dispatcher with only one thread it breaks every single time)I’ve done some small and quick tests so I’m not sure if it is 100% fixed, but looks like it was fixed! Thanks @Tapac :3