You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I'm facing a tricky issue when I try to run multiple parallel queries using ScalikeJDBC's Query DSL. In essence, the code in SQLSyntaxSupportFeature.columns deadlocks against my DB.autoCommit() calls, and everything hangs until the connection pool throws with the "Timeout waiting for idle object" error.
Postgres table (selection of DB engine probably doesn't matter):
createtabletimeRecords (timetimestamp with time zonenot null unique);
build.sbt:
ThisBuild/ version :="0.1.0-SNAPSHOT"ThisBuild/ scalaVersion :="3.1.3"lazyvalroot= (project in file("."))
.settings(
name :="ScalikeColumnsDeadlock"
)
scalacOptions ++=Seq("-deprecation", "-unchecked", "-feature", "-explaintypes")
libraryDependencies ++=Seq(
"com.typesafe.scala-logging"%%"scala-logging"%"3.9.4",
"ch.qos.logback"%"logback-classic"%"1.2.10",
"org.scalikejdbc"%%"scalikejdbc"%"4.0.+",
"org.postgresql"%"postgresql"%"42.3.+",
)
Here, we are trying to run 10 queries in parallel. The Commons2 connection pool uses 8 connections by default. As far as I understand, the code successfully obtains all 8 connections in parallel DB.autoCommit() calls (the other 2 DB.autoCommit() calls start waiting for a free connection). Then, the code in TimeRecordDAO.createTimeRecord() references _c_.time, which (as far as I understand) translates to a call to SQLSyntaxSupportFeature.field(), which ultimately references the lazy variable ColumnSQLSyntaxProvider.columns, whose initialization code, in turn, calls SQLSyntaxSupport.columns. And this property, finally, tries to borrow another database connection to load the list of table's columns. Since there are no available connections in the pool, this code blocks. All other threads that already acquired their connections in DB.autoCommit() also wait for the initialization code of ColumnSQLSyntaxProvider.columns to complete. This way, each of these 10 threads is stuck, either waiting for a connection in DB.autoCommit(), or waiting for a connection in SQLSyntaxSupport.columns, or waiting for completion of initialization code of ColumnSQLSyntaxProvider.columns.
When the pool declares timeout, 7 of 8 calls that acquired connections in DB.autoCommit() successfully complete their queries, and one of them fails for some reason. The other 2 DB.autoCommit() calls predictably fail.
Also, if I query 100 tasks instead of 10 tasks (within the same 10-thread ExecutionContext), most of them fail to insert a new record, not sure why.
The root problem here is that SQLSyntaxSupportFeature/ColumnSQLSyntaxProvider/SQLSyntaxSupport rely on the possibility to acquire a new connection from a pool by themselves, at an arbitrary moment of time. This issue would not occur if these classes used a session/connection obtained from the caller instead.
The text was updated successfully, but these errors were encountered:
Hi @pvgoran, thanks for sharing this with details. Your analysis looks valid to me, too.
However, due to the current library design, passing the already acquired connection for column name loading may not be quickly feasible. I can't afford the time to make code changes because of my life priorities. If anyone is interested in improving this without breaking changes, I am happy to be a reviewer, though.
A workaround for this use case now would be explicitly listing the column names (meaning overriding columns) in the class instead of relying on the default auto-loading feature.
However, due to the current library design, passing the already acquired connection for column name loading may not be quickly feasible.
Yes, that's what I thought. It probably requires breaking changes (or at least introducing alternatives for multiple methods/properties).
A workaround for this use case now would be explicitly listing the column names (meaning overriding columns) in the class instead of relying on the default auto-loading feature.
Another work-around would be to manually reference SQLSyntaxSupport.columns in all objects/instances after configuring the pool (before doing any real database access). (I didn't actually try this, so maybe it won't work.)
@seratch Are there any (other) active ScalikeJDBC developers?
I am the sole maintainer who makes code changes these days. I am still committed to working on urgent issues such as critical security bugs. However, I don't see such severity on this possible problem mentioned here because production-grade apps tend to have a large enough connection pool size, so with that, the problem rarely occurs.
I do understand this issue can be a major one for you. However, for the above reasons, I won't work on improvements in the short term. If this potential issue is critical for you, plus you don't have time to contribute to this library, please consider switching to a different Scala library.
Again, I appreciate your time here to report this issue with great insights.
I'm facing a tricky issue when I try to run multiple parallel queries using ScalikeJDBC's Query DSL. In essence, the code in
SQLSyntaxSupportFeature.columns
deadlocks against myDB.autoCommit()
calls, and everything hangs until the connection pool throws with the "Timeout waiting for idle object" error.Postgres table (selection of DB engine probably doesn't matter):
build.sbt
:main.scala
:Output:
Here, we are trying to run 10 queries in parallel. The Commons2 connection pool uses 8 connections by default. As far as I understand, the code successfully obtains all 8 connections in parallel
DB.autoCommit()
calls (the other 2DB.autoCommit()
calls start waiting for a free connection). Then, the code inTimeRecordDAO.createTimeRecord()
references_c_.time
, which (as far as I understand) translates to a call toSQLSyntaxSupportFeature.field()
, which ultimately references the lazy variableColumnSQLSyntaxProvider.columns
, whose initialization code, in turn, callsSQLSyntaxSupport.columns
. And this property, finally, tries to borrow another database connection to load the list of table's columns. Since there are no available connections in the pool, this code blocks. All other threads that already acquired their connections inDB.autoCommit()
also wait for the initialization code ofColumnSQLSyntaxProvider.columns
to complete. This way, each of these 10 threads is stuck, either waiting for a connection inDB.autoCommit()
, or waiting for a connection inSQLSyntaxSupport.columns
, or waiting for completion of initialization code ofColumnSQLSyntaxProvider.columns
.When the pool declares timeout, 7 of 8 calls that acquired connections in
DB.autoCommit()
successfully complete their queries, and one of them fails for some reason. The other 2DB.autoCommit()
calls predictably fail.Also, if I query 100 tasks instead of 10 tasks (within the same 10-thread ExecutionContext), most of them fail to insert a new record, not sure why.
The root problem here is that
SQLSyntaxSupportFeature
/ColumnSQLSyntaxProvider
/SQLSyntaxSupport
rely on the possibility to acquire a new connection from a pool by themselves, at an arbitrary moment of time. This issue would not occur if these classes used a session/connection obtained from the caller instead.The text was updated successfully, but these errors were encountered: