Skip to content

Commit

Permalink
Connection release for JDBC should be not cancelable (#129)
Browse files Browse the repository at this point in the history
  • Loading branch information
kgribov committed May 7, 2024
1 parent 8d682a6 commit 1e0853f
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 2 deletions.
2 changes: 1 addition & 1 deletion buildSrc/src/main/kotlin/CI.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import java.lang.Boolean.parseBoolean
object Ci {

private const val SNAPSHOT_BASE = "0.7.0"
private const val RELEASE_VERSION = "0.6.7"
private const val RELEASE_VERSION = "0.6.8"
private val githubSha = System.getenv("GITHUB_SHA") ?: "latest"

val publishRelease = System.getProperty("release", "true").let(::parseBoolean)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.razz.eva.persistence.jdbc
import com.zaxxer.hikari.HikariDataSource
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.withContext
import java.sql.Connection

Expand All @@ -18,7 +19,9 @@ class HikariPoolConnectionProvider(
}

override suspend fun release(connection: Connection) =
withContext(blockingJdbcContext) {
// If we close current coroutine (by service/http/call/etc timeout f.e.) -
// we can't call withContext() block, because it will throw an exception.
withContext(blockingJdbcContext + NonCancellable) {
connection.close()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import io.mockk.mockk
import io.mockk.verify
import io.mockk.verifyOrder
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import java.sql.Connection

Expand Down Expand Up @@ -276,6 +279,55 @@ class JdbcTransactionManagerSpec : BehaviorSpec({
}
}

And("Long running action") {
var count = 0
val actionStarted = Channel<Boolean>(1)
val action = suspend {
actionStarted.send(true)
delay(100_000)
count++
}

And("Principal runs action with connection in cancelable coroutine") {
clearMocks(primaryPool, answers = false)
clearMocks(replicaPool, answers = false)

val connection = mockk<Connection>(relaxed = true)
every { replicaPool.connection } returns connection
val coroutine = async {
jdbcTransactionManager.withConnection { _ -> action() }
}

When("Principal cancels coroutine") {
actionStarted.receive()
coroutine.cancel()

Then("Count logic was not called") {
count shouldBe 0
}

And("Connection was acquired and released") {
verifyOrder {
replicaPool.connection
connection.close()
}
}

And("Only one pool connection was acquired and returned") {
verify(exactly = 1) {
replicaPool.connection
}
verify(exactly = 1) {
connection.close()
}
confirmVerified(primaryPool)
confirmVerified(replicaPool)
confirmVerified(connection)
}
}
}
}

And("Transactional action throwing error") {
val bad = suspend {
jdbcTransactionManager.withConnection {
Expand Down

0 comments on commit 1e0853f

Please sign in to comment.