Skip to content

fix(database): Drop a closed connection kept by DefaultPrepAndExpecte… - #963

Merged
jeffjensen merged 2 commits into
releases/3.5.xfrom
962-reusable-connection-liveness
Aug 31, 2026
Merged

fix(database): Drop a closed connection kept by DefaultPrepAndExpecte…#963
jeffjensen merged 2 commits into
releases/3.5.xfrom
962-reusable-connection-liveness

Conversation

@jeffjensen

@jeffjensen jeffjensen commented Aug 31, 2026

Copy link
Copy Markdown
Member

Fixes #962. Targets releases/3.5.x for a 3.5.2 patch release.

DefaultPrepAndExpectedTestCase with setCloseConnectionAfterTest(false), reused across test methods (a base-class or shared static field paired with a CachingConnectionProvider), pinned the first IDatabaseConnection it acquired in a field and never re-checked it. closeReusableConnection() deliberately keeps that field set — not closing it — when closeConnectionAfterTest is false, with no check that the kept connection is still open. So once the pool or the database dropped that connection between test methods (max-lifetime/reap, a PGConnectionPoolDataSource issuing a fresh logical handle, a bounced application context, an idle-in-transaction kill), every following test on the instance failed on it: in setupData()'s CLEAN_INSERT, then again (suppressed) in cleanupData()'s tear down. CachingConnectionProvider's own liveness check, which exists to replace a dead connection transparently, was never reached because the provider is not consulted again once the field is set.

  • getReusableConnection() discards the cached connection when closeConnectionAfterTest is false and it reports isClosed(), so the next call re-acquires from the tester. isClosed() only — cheap, no round trip, and it doesn't break the "acquire once, no provider" contract a round-tripping isValid() would.
  • closeReusableConnectionSuppressing() (only ever called from an already-failed lifecycle step) now also forgets the connection even when closeConnectionAfterTest is false — so a server-side disconnect the driver reported only by throwing still lets the next test re-acquire, not just one the driver's local isClosed() flag catches.
  • Default closeConnectionAfterTest=true path unchanged.
  • Two new DatabaseTesterConnectionReuseIT cases: stable reuse while alive, and replacement after a between-tests close.

Present since 3.4.0 (setCloseConnectionAfterTest). Also affects main (3.6.0), where it additionally surfaces in the row count check's baseline capture — a separate forward-port PR will follow.

Summary by Sourcery

Ensure reused database test cases recover transparently when their cached connection becomes unavailable.

Bug Fixes:

  • Prevent reused test cases from retaining and reusing database connections that were closed between test methods.
  • Forget cached connections after failed lifecycle steps so subsequent tests can acquire a replacement.

Build:

  • Bump the project version to 3.5.2-SNAPSHOT.

Tests:

  • Add integration coverage for connection reuse while healthy and replacement after the cached connection is closed.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@sourcery-ai

sourcery-ai Bot commented Aug 31, 2026

Copy link
Copy Markdown

Reviewer's Guide

Fix reused DefaultPrepAndExpectedTestCase instances that retain a closed connection by checking the cached JDBC connection with isClosed(), discarding it when necessary, and reacquiring through the configured tester/provider; integration tests cover both stable reuse and replacement after connection loss.

Sequence diagram for replacing a closed reusable database connection

sequenceDiagram
    participant TestCase as DefaultPrepAndExpectedTestCase
    participant Tester as databaseTester
    participant Provider as CachingConnectionProvider
    participant JDBC as JDBCConnection

    TestCase->>TestCase: getReusableConnection()
    TestCase->>JDBC: isClosed()
    alt cached connection is closed
        TestCase->>Tester: getConnection()
        Tester->>Provider: getConnection()
        Provider-->>Tester: live replacement
        Tester-->>TestCase: replacement connection
    else cached connection is open
        TestCase-->>TestCase: reuse cached connection
    end
    TestCase->>TestCase: closeReusableConnection()
    Note over TestCase: With closeConnectionAfterTest=false, the open connection remains cached
Loading

File-Level Changes

Change Details Files
Detect and replace dead cached connections when connection reuse is enabled.
  • Check the cached JDBC connection with local isClosed() state before reuse.
  • Clear the cached IDatabaseConnection when it is closed or its state check throws, allowing the tester/provider to reacquire a replacement.
  • Preserve the existing default behavior that closes and forgets connections after each test.
src/main/java/org/dbunit/DefaultPrepAndExpectedTestCase.java
Add integration coverage for connection reuse and recovery after connection loss.
  • Verify a reused test case continues using one open connection across multiple simulated test methods.
  • Verify a closed cached connection is replaced before the next method, including row-count baseline and teardown lifecycle steps.
  • Add a DataSource test fixture that supplies fresh JDBC connections for the reuse scenarios.
src/test/java/org/dbunit/DatabaseTesterConnectionReuseIT.java
Document the closed-connection reuse fix in the release changes.
  • Add the issue 962 fix to the 3.5.2 release notes and describe the affected reuse configuration and recovery behavior.
src/changes/changes.xml

Assessment against linked issues

Issue Objective Addressed Explanation
#962 Ensure that a DefaultPrepAndExpectedTestCase reused across test methods with closeConnectionAfterTest=false detects a cached IDatabaseConnection that has been closed and discards it before reuse.
#962 Re-acquire a replacement connection through the database tester and CachingConnectionProvider after the cached connection is dropped, allowing subsequent test lifecycle operations to succeed instead of repeatedly failing on the dead connection.
#962 Provide regression coverage for both continued reuse of a live connection and replacement of a connection closed between test methods.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Aug 31, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e176b447-ac46-4116-b46a-2b4ba976c998

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7d958251-3025-41ef-a7d4-49cb05877e9a

📥 Commits

Reviewing files that changed from the base of the PR and between 2cca729 and 8b9b0ab.

📒 Files selected for processing (3)
  • src/changes/changes.xml
  • src/main/java/org/dbunit/DefaultPrepAndExpectedTestCase.java
  • src/test/java/org/dbunit/DatabaseTesterConnectionReuseIT.java

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The change updates DefaultPrepAndExpectedTestCase to discard closed cached connections before reuse. Integration tests cover both continued reuse of open connections and reacquisition after connection closure. The 3.5.2 changelog records the fix.

Changes

Connection reuse handling

Layer / File(s) Summary
Cached connection validation
src/main/java/org/dbunit/DefaultPrepAndExpectedTestCase.java, src/changes/changes.xml
getReusableConnection() checks cached connection state when connection closing is disabled. Closed connections, or connections that fail the state check, are discarded before reacquisition. The changelog records the fix.
Connection reuse integration tests
src/test/java/org/dbunit/DatabaseTesterConnectionReuseIT.java
Integration tests verify reuse of one open connection across simulated test methods and replacement of a cached connection after it closes. Test helpers configure row-count checking, a shared provider, and a ProfileDataSource backed by DriverManager.

Estimated code review effort: 2 (Simple) | ~15 minutes

Merge Risk: ⚪ Minimal · up to 8b9b0

The change replaces explicitly closed cached test connections while preserving reuse of live connections, preventing repeated failures in subsequent database tests. No actionable merge-blocking risk remains beyond normal checks and review.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.05% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 19 functions across 2 files. (1 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: dropping a closed cached connection in DefaultPrepAndExpectedTestCase. It is specific and related to the stale connection fix.
Full details: Docstring Coverage

Explanation

Docstring coverage is 21.05% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 19 functions across 2 files. (1 skipped: 1 unsupported.)

✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 962-reusable-connection-liveness

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

sourcery-ai[bot]
sourcery-ai Bot previously approved these changes Aug 31, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="src/main/java/org/dbunit/DefaultPrepAndExpectedTestCase.java" line_range="340" />
<code_context>
  */
 package org.dbunit;

</code_context>
<issue_to_address>
**nitpick:** The new private method is documented with `@since 3.6.0`, while the changelog places this fix in the 3.5.2 release. Generated API/source documentation therefore reports the method as introduced in a release that does not contain this change.

**Suggested fix:** Change the tag to the release containing this fix, or omit `@since` for this private helper.
</issue_to_address>

Sourcery assessment

Approved.


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/main/java/org/dbunit/DefaultPrepAndExpectedTestCase.java

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="src/main/java/org/dbunit/DefaultPrepAndExpectedTestCase.java" line_range="345" />
<code_context>
+    {
+        try
+        {
+            return connection.getConnection().isClosed();
+        } catch (final SQLException e)
+        {
</code_context>
<issue_to_address>
**issue (bug_risk):** When the database or pool invalidates the connection without the JDBC driver changing `isClosed()` to true, `isReusableConnectionClosed()` returns false and the dead cached connection is returned indefinitely. The subsequent statement fails, but the cached field is not cleared and `CachingConnectionProvider` is never consulted to validate or replace it.

**Triggers:** When a server-side disconnect is not reflected by the driver's local `isClosed()` flag.

**Suggested fix:** Either invalidate and reacquire the cached connection when a lifecycle operation fails, or narrow the documentation and test expectations to connections whose local closed flag is updated.
</issue_to_address>

Sourcery assessment

Approval pending. 1 finding to address first.

Blocking findings: src/main/java/org/dbunit/DefaultPrepAndExpectedTestCase.java:345


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

{
try
{
return connection.getConnection().isClosed();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): When the database or pool invalidates the connection without the JDBC driver changing isClosed() to true, isReusableConnectionClosed() returns false and the dead cached connection is returned indefinitely. The subsequent statement fails, but the cached field is not cleared and CachingConnectionProvider is never consulted to validate or replace it.

Triggers: When a server-side disconnect is not reflected by the driver's local isClosed() flag.

Suggested fix: Either invalidate and reacquire the cached connection when a lifecycle operation fails, or narrow the documentation and test expectations to connections whose local closed flag is updated.

jeffjensen and others added 2 commits August 31, 2026 10:05
The 3.5.1 release shipped from this branch; development toward the 3.5.2
patch continues here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q77iHoBhzZxJCQzkC8f82f
…dTestCase

DefaultPrepAndExpectedTestCase with setCloseConnectionAfterTest(false),
reused across test methods (a base-class or shared static field paired with
a CachingConnectionProvider), pinned the first IDatabaseConnection it
acquired in a field and never re-checked it. closeReusableConnection()
deliberately keeps that field set - not closing it - when
closeConnectionAfterTest is false, with no check that the kept connection is
still open. So once the pool or the database dropped that connection between
test methods (max-lifetime/reap, a PGConnectionPoolDataSource issuing a
fresh logical handle, a bounced application context, an idle-in-transaction
kill), every following test on the instance failed on it: in setupData()'s
CLEAN_INSERT, then again, suppressed, in cleanupData()'s tear down
operation. CachingConnectionProvider's own liveness check, which exists to
replace a dead connection transparently, was never reached, because the
provider is not consulted again once the field is set.

* getReusableConnection() discards the cached connection when
  closeConnectionAfterTest is false and it reports isClosed(), so the next
  call re-acquires from the tester and a CachingConnectionProvider behind it
  hands back a live replacement. isClosed() only - cheap, no round trip, and
  it does not break the "acquire once, no provider" contract a
  round-tripping isValid() would.
* closeReusableConnectionSuppressing(), only ever called from a lifecycle
  step that has already failed, now also forgets the connection even when
  closeConnectionAfterTest is false - so a server-side disconnect the driver
  reported only by throwing (not by flipping isClosed()) still lets the next
  test re-acquire, rather than depending on the driver's local closed flag.
* The default closeConnectionAfterTest=true path is unchanged.
* Add two DatabaseTesterConnectionReuseIT cases: one proving a reused
  instance keeps reusing its one open connection while it stays alive, one
  proving it replaces the connection after it is closed between test methods
  instead of failing every following test on the dead one.

Refs: 962

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q77iHoBhzZxJCQzkC8f82f
@jeffjensen
jeffjensen changed the base branch from main to releases/3.5.x August 31, 2026 15:06
@jeffjensen
jeffjensen force-pushed the 962-reusable-connection-liveness branch from 8b9b0ab to b0a9ff3 Compare August 31, 2026 16:18
@sourcery-ai
sourcery-ai Bot dismissed their stale review August 31, 2026 16:18

Sourcery withdrew this approval because the latest commits introduced blocking findings.

@jeffjensen
jeffjensen merged commit aa6da42 into releases/3.5.x Aug 31, 2026
27 checks passed
@jeffjensen
jeffjensen deleted the 962-reusable-connection-liveness branch August 31, 2026 16:23
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.

DefaultPrepAndExpectedTestCase keeps reusing a closed connection after the pool or server drops it between test methods

1 participant