Skip to content

fix leaked Statement/ResultSet in JdbcSessionUtil.tryWithResource - #618

Open
pjfanning wants to merge 2 commits into
apache:mainfrom
pjfanning:jdbc-try-with-resource
Open

pjfanning wants to merge 2 commits into
apache:mainfrom
pjfanning:jdbc-try-with-resource

Conversation

@pjfanning

@pjfanning pjfanning commented Sep 6, 2026 •

Copy link
Copy Markdown
Member

Motivation

JdbcSessionUtil.tryWithResource takes the resource as a by-name parameter and evaluates it twice: once when calling func, and once again in the finally block.

def tryWithResource[T, C <: AutoCloseable](closeable: => C)(func: C => T): T = {
  try { func(closeable) }          // evaluates the by-name arg -> resource #1
  finally { try closeable.close()  // evaluates it AGAIN -> resource #2, closes #2

So the resource handed to func is never closed, and a second one is created just to be closed. Consequences:

  • JdbcOffsetStore (lines 69, 80, 98, 114, 182, 200, 240, 281, 301) and SlickOffsetStore (lines 169, 176, 187, 193) leak a Statement/PreparedStatement on every call. When the JdbcSession is backed by a connection pool (connection returned to the pool rather than physically closed) these accumulate on the physical connection and can exhaust server-side cursors.
  • tryWithResource(stmt.executeQuery()) in JdbcOffsetStore.readOffset and readManagementState executes the query a second time and leaks the first ResultSet.

Modification

Evaluate the by-name parameter once into a local val, and use that instance both for func and for close().

Result

The resource passed to func is the one that gets closed, no extra resource is created, and each query is executed once.

Tests

  • New JdbcSessionUtilSpec asserts that exactly one resource is created and that the instance passed to the function is the one closed (also on function failure, and when close() throws SQLException). All 3 cases fail before the fix.
  • sbt "jdbc/test" - 31 passed
  • sbt "jdbc/mimaReportBinaryIssues" - success
  • sbt checkCodeStyle - success
  • sbt headerCreateAll - header added to the new spec

References

None - found during a resource-leak audit of the main sources

pjfanning added a commit to pjfanning/incubator-pekko-projection that referenced this pull request Sep 6, 2026
Motivation:
The new spec file was untracked when `scalafmt --mode diff-ref` was run, so it
was not reformatted and the `Code is formatted` and `Check Code Style` jobs
failed.

Modification:
Replace the matcher form that scalafmt rewrites into `(x should be).theSameInstanceAs(y)`
with a plain reference-equality assertion, and format the file.

Result:
`sbt checkCodeStyle` passes.

Tests:
- sbt checkCodeStyle - success
- sbt "jdbc/test" - 31 passed

References:
Refs apache#618
@pjfanning pjfanning added this to the 2.0.0-M1 milestone Oct 25, 2026
@pjfanning
pjfanning requested a review from ptrdom October 26, 2026 15:12
Motivation:
`tryWithResource` takes the resource as a by-name parameter and evaluates it
twice: once when calling `func`, and once again in the `finally` block. The
resource handed to `func` is therefore never closed, while a second resource is
created only to be closed immediately. Every call site in `JdbcOffsetStore` and
`SlickOffsetStore` leaks a `Statement`/`PreparedStatement`, which accumulates on
the physical connection when the `JdbcSession` is backed by a connection pool.
For `tryWithResource(stmt.executeQuery())` in `JdbcOffsetStore.readOffset` and
`readManagementState` it also means the query is executed twice and the first
`ResultSet` is leaked.

Modification:
Evaluate the by-name parameter once into a local val and use that instance for
both `func` and `close()`.

Result:
The resource passed to `func` is the one that gets closed, no extra resource is
created, and queries are executed once.

Tests:
- sbt "jdbc/test" - 31 passed (new JdbcSessionUtilSpec fails on all 3 cases without the fix)
- sbt "jdbc/mimaReportBinaryIssues" - success
- scalafmt --mode diff-ref=upstream/main - no changes
- sbt headerCreateAll - header added to the new spec

References:
None - found during a resource-leak audit of the main sources
Motivation:
The new spec file was untracked when `scalafmt --mode diff-ref` was run, so it
was not reformatted and the `Code is formatted` and `Check Code Style` jobs
failed.

Modification:
Replace the matcher form that scalafmt rewrites into `(x should be).theSameInstanceAs(y)`
with a plain reference-equality assertion, and format the file.

Result:
`sbt checkCodeStyle` passes.

Tests:
- sbt checkCodeStyle - success
- sbt "jdbc/test" - 31 passed

References:
Refs apache#618
@pjfanning
pjfanning force-pushed the jdbc-try-with-resource branch from 47ceecc to d648454 Compare October 27, 2026 08:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant