fan*_*f42 7 concurrency deadlock hikaricp doobie zio
我在 ZIO 应用程序中使用 Doobie,有时会出现死锁(应用程序完全冻结)。如果我仅在一个内核上运行我的应用程序,或者达到与数据库的最大并行连接数,就会发生这种情况。
我的代码看起来像:
def mkTransactor(cfg: DatabaseConfig): RManaged[Blocking, Transactor[Task]] =
ZIO.runtime[Blocking].toManaged_.flatMap { implicit rt =>
val connectEC = rt.platform.executor.asEC
val transactEC = rt.environment.get.blockingExecutor.asEC
HikariTransactor
.fromHikariConfig[Task](
hikari(cfg),
connectEC,
Blocker.liftExecutionContext(transactEC)
)
.toManaged
}
private def hikari(cfg: DatabaseConfig): HikariConfig = {
val config = new com.zaxxer.hikari.HikariConfig
config.setJdbcUrl(cfg.url)
config.setSchema(cfg.schema)
config.setUsername(cfg.user)
config.setPassword(cfg.pass)
config
}
Run Code Online (Sandbox Code Playgroud)
或者,我在 Hikari ( config.setLeakDetectionThreshold(10000L))上设置了泄漏检测参数,但出现泄漏错误不是由于处理数据库查询所花费的时间。
Doobie 文档中有关于执行上下文和对每个上下文的期望的很好的解释:https : //tpolecat.github.io/doobie/docs/14-Managing-Connections.html#about-transactors
根据文档,“等待连接到数据库的执行上下文”(connectEC在问题中)应该是有界的。
ZIO,默认情况下,只有两个线程池:
zio-default-async – 有界,zio-default-blocking – 无界所以很自然地相信我们应该使用zio-default-async它,因为它是有界的。
不幸的是,zio-default-async假设它的操作永远不会阻塞。这非常重要,因为它是ZIO解释器(其运行时)用来运行的执行上下文。如果你阻止它,你实际上可以阻止程序的评估进程ZIO。当只有一个内核可用时,这种情况会更频繁地发生。
问题是等待 DB 连接的执行上下文意味着阻塞,等待 Hikari 连接池中的可用空间。所以我们不应该使用zio-default-async这个执行上下文。
下一个问题是:只为connectEC?创建一个新的线程池和相应的执行上下文有意义吗?没有什么禁止您这样做,但可能没有必要,原因有以下三个:
您希望避免创建线程池,特别是因为您可能已经从 Web 框架、数据库连接池、调度程序等创建了多个线程池。每个线程池都有其成本。一些例子是:
ZIO 线程池人体工程学开始针对它们的使用进行很好的优化
在一天结束时,您将不得不在某处管理您的超时,并且连接不是系统中最有可能拥有足够信息来知道它应该等待多长时间的部分:不同的交互(即,在应用程序的外部部分,更接近使用点)可能需要不同的超时/重试逻辑。
综上所述,我们发现了一个在生产环境中运行的应用程序中运行良好的配置:
// zio.interop.catz._ provides a `zioContextShift`
val xa = (for {
// our transaction EC: wait for aquire/release connections, must accept blocking operations
te <- ZIO.access[Blocking](_.get.blockingExecutor.asEC)
} yield {
Transactor.fromDataSource[Task](datasource, te, Blocker.liftExecutionContext(te))
}).provide(ZioRuntime.environment).runNow
def transactTask[T](query: Transactor[Task] => Task[T]): Task[T] = {
query(xa)
}
Run Code Online (Sandbox Code Playgroud)
我绘制了 Doobie 和 ZIO 执行上下文如何相互映射:https : //docs.google.com/drawings/d/1aJAkH6VFjX3ENu7gYUDK-qqOf9-AQI971EQ4sqhi2IY
更新:我在这里创建了一个包含 3 个该模式使用示例(混合应用程序、纯应用程序、ZLayer 应用程序)的存储库:https : //github.com/fanf/test-zio-doobie 欢迎任何反馈。