zio-jdbc icon indicating copy to clipboard operation
zio-jdbc copied to clipboard

ZIO failure in `ZConnectionPool.transaction`

Open sullivan- opened this issue 5 months ago • 2 comments

I'm getting an InterruptedException in ZConnectionPool.transaction. I am unable to mapError or tapError on the failure. When I run it unsafe, I get a zio.Exit.Failure. Nothing I have tried has allowed me to see the cause of the failure, e.g., the stack trace of the InterruptedException.

I've boiled my problem down to a pretty minimal test case. If I take out the debugging code, which I thought you might find helpful, it comes in well under 50 lines. I am using zio version 2.1.9 and zio-jdbc version 0.1.2

import zio.{ ZIO, ZLayer }
import zio.jdbc.{ ZConnectionPool, ZConnectionPoolConfig, stringToSql }
import zio.test.Assertion.equalTo
import zio.test.{ ZIOSpecDefault, assert }

class Api(val connectionPool: ZConnectionPool) {
  val query     = stringToSql("SELECT 'foo'").query[String]
  val selectOne = connectionPool.transaction(query.selectOne)

  val fooZIO: ZIO[Any, Throwable, Option[String]] = for {
    _   <- ZIO.debug("before selectOne")
    foo <- selectOne.tapError(e => ZIO.debug(s"tapError selectOne $e"))
    _   <- ZIO.debug("after selectOne")
  } yield foo
}

object ApiSpec extends ZIOSpecDefault {
  val connectionPoolConfigLayer = ZLayer.succeed(ZConnectionPoolConfig.default)

  val connectionPoolLayer = ZConnectionPool.postgres(
    "localhost", 5432, "test", Map("user" -> "test", "password" -> "test")
  )

  val apiLayer = ZLayer(ZIO.service[ZConnectionPool].map { connectionPool =>
    new Api(connectionPool)
  })

  val layer = connectionPoolConfigLayer >>> connectionPoolLayer >>> apiLayer

  zio.Unsafe.unsafe { implicit unsafe =>
    val z = zio.Runtime.default.unsafe.run {
      for {
        _ <- ZIO.debug("before")
        z <- ZIO.service[Api].provide(layer).flatMap(_.fooZIO)
          .tapError(e => ZIO.debug(s"EEE $e"))
          .tapDefect(e => ZIO.debug(s"EEE $e"))
          .tapBoth(e => ZIO.debug(s"EEE $e"), e => ZIO.debug(s"EEE $e"))
          
        _ <- ZIO.debug("after")
      } yield z
    }
    println(z)
  }

  def spec = test("api.fooZIO should be Some(foo)") {
    for {
      api      <- ZIO.service[Api].provide(layer)
      _        <- ZIO.debug("before login")
      response <- api.fooZIO
      _        <- ZIO.debug("after login")
    } yield {
      assert(response)(equalTo(Some("foo")))
    }
  }

}

Here is the output when I run sbt test:

before
before selectOne
Failure(Both(Empty,Both(Empty,Interrupt(Runtime(378775985,1727107547164,zio.jdbc.ZConnectionPool.make.tx(ZConnectionPool.scala:168)),Stack trace for thread "zio-fiber-378775985,2016155100,708221690,403450518":
	at zio.jdbc.ZConnectionPool.make.tx(ZConnectionPool.scala:168)
	at <empty>.Api.selectOne(ApiSpec.scala:8)
	at <empty>.Api.fooZIO(ApiSpec.scala:12)
	at <empty>.Api.fooZIO(ApiSpec.scala:14)
	at <empty>.ApiSpec.<local ApiSpec$>.z(ApiSpec.scala:35)
	at <empty>.ApiSpec.<local ApiSpec$>.z(ApiSpec.scala:36)
	at <empty>.ApiSpec.<local ApiSpec$>.z(ApiSpec.scala:37)
	at <empty>.ApiSpec.<local ApiSpec$>.z(ApiSpec.scala:40)
	at <empty>.ApiSpec.<local ApiSpec$>.z(ApiSpec.scala:41)))))
before login
before selectOne
- api.fooZIO should be Some(foo)
  Exception in thread "zio-fiber-59,58,56,51" java.lang.InterruptedException: Interrupted by thread "zio-fiber-59"
  	at zio.jdbc.ZConnectionPool.make.tx(ZConnectionPool.scala:168)
  	at <empty>.Api.selectOne(ApiSpec.scala:8)
  	at <empty>.Api.fooZIO(ApiSpec.scala:12)
  	at <empty>.Api.fooZIO(ApiSpec.scala:14)
  	at <empty>.ApiSpec.spec(ApiSpec.scala:53)
  	at <empty>.ApiSpec.spec(ApiSpec.scala:54)
0 tests passed. 1 tests failed. 0 tests ignored.


- api.fooZIO should be Some(foo)
  Exception in thread "zio-fiber-59,58,56,51" java.lang.InterruptedException: Interrupted by thread "zio-fiber-59"
  	at zio.jdbc.ZConnectionPool.make.tx(ZConnectionPool.scala:168)
  	at <empty>.Api.selectOne(ApiSpec.scala:8)
  	at <empty>.Api.fooZIO(ApiSpec.scala:12)
  	at <empty>.Api.fooZIO(ApiSpec.scala:14)
  	at <empty>.ApiSpec.spec(ApiSpec.scala:53)
  	at <empty>.ApiSpec.spec(ApiSpec.scala:54)
Executed in 564 ms

[info] Completed tests
[error] Failed tests:
[error] 	ApiSpec
[error] (Test / testOnly) sbt.TestsFailedException: Tests unsuccessful

You will note from the output that the error is completely untappable. It's possible that there is something wrong with my zio-jdbc code and/or Postgres setup, and there is a real error hidden in there somewhere. But I highly doubt it because, inexplicably, I have another test that does nearly the exact same thing, and it passes. And I mean, it passes every time. And this test I shared here fails every time.

The output shows line 168 in ZConnectionPool.scala. That's from zio-jdbc version 0.1.2. Poking a bit in the debugger, the error seems to arise from ZConnection.close.

Whatever is going on, it seems pretty clear that there is a zio-jdbc bug in here somewhere, because I really should be able to map or tap the error.

sullivan- avatar Sep 23 '24 16:09 sullivan-