Skip to content

Improve MultiDbTransaction functionality and test coverage - #4561

Merged
atakavci merged 17 commits into
redis:masterfrom
atakavci:ali/improveMultidbTran
Jun 29, 2026
Merged

atakavci merged 17 commits into
redis:masterfrom
atakavci:ali/improveMultidbTran

Conversation

@atakavci

@atakavci atakavci commented Jun 8, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Improves the behavior and functionality of MultiDbTransaction so it matches the semantics users expect from a Redis transaction, and adds the test coverage to back it.

WATCH, UNWATCH and any pre-MULTI commands are now first-class operations on the transaction object — they execute immediately on a lazily-acquired connection and return their real responses to the caller, while post-MULTI commands stay buffered until exec(). This enables the canonical watch → read → multi → write → exec flow through a single transaction handle.

What changed in MultiDbTransaction

  • Lazy connection acquisition. With doMulti=true, deferred until exec(). With doMulti=false, acquired on the first command that needs the server. The same connection is reused for the whole lifecycle.
  • Unified command path. Everything goes through appendCommand: Response.of / Response.error for immediate commands, buffered Responses for queued ones.
  • MULTI reply validated by byte-array equality against OK; a non-OK reply now surfaces as JedisDataException instead of being silently treated as success.
  • watch(...) inside MULTI is rejected with IllegalStateException. unwatch() inside MULTI is buffered like any other queued command and returns null (its reply arrives via exec()).
  • discard() contacts the server only when needed — sends UNWATCH if a connection was already held for WATCH, otherwise clears local state only. Never sends DISCARD for a transaction that was never started server-side.
  • Database-switch protection. The active database is pinned at acquisition. If it changes mid-transaction, the next pre-MULTI command and the pre-EXEC check both throw JedisException("Active database has changed since transaction started"). On exec() a compensating DISCARD is sent on the original connection before aborting.
  • Pool poisoning prevented. If exec() fails leaving the server in MULTI (e.g. EXECABORT, or the compensating DISCARD also fails), the held connection is marked broken before release so the pool invalidates it instead of returning a mid-transaction connection to idle.

Supporting production change

  • Response.of / Response.error promoted to public so MultiDbTransaction (in a different package) can build already-decoded responses for pre-MULTI commands instead of mutating internal state.

Tests

MultiDbPipeline and MultiDbTransaction previously had no dedicated unit or integration suites. This PR introduces both.

MultiDbTransaction — new coverage for the reworked behavior

Unit (mocked connection + pool, no I/O):

  • Happy path: MULTI via executeCommand(CommandObject) returns OK byte[], getMany(...) returns QUEUED byte[]s, EXEC formats replies through each buffered Response.
  • null-reply (WATCH-abort) path.
  • WATCH → close → UNWATCH on a single eagerly-acquired connection.
  • Illegal-state guards on exec() / discard() without MULTI.
  • Deferred-acquisition discard() / close() never touches the pool.
  • Validation-failure path still returns the borrowed connection.

Integration (real Redis):

  • Queued commands execute; discard() rolls them back.
  • close() while in MULTI issues a discard.
  • exec() and discard() return the connection to the active pool.
  • WATCH → MULTI → EXEC flow.
  • Queue-time server rejections propagate as errors with suppressed causes.
  • Failed exec() does not poison the pooled connection on subsequent reuse.

MultiDbPipeline — baseline coverage (no production change)

Unit:

  • Connection accounting under retry / circuit-breaker — every connection borrowed during repeated validation failures is returned to the pool.
  • Transient validation failures recovered by retry.
  • Connection released when sendCommand or getMany throws mid-flush.
  • Empty-sync / empty-close no-ops; happy path; close() triggers sync(); ping-failure propagation.

Integration:

  • Queued execution with mixed reply types.
  • Pool return on sync() / close().
  • Idempotency of repeated sync() on the same pipeline.
  • Response.get() contract — IllegalStateException before sync(), real value after.

Note

Medium Risk
Changes experimental multi-DB transaction and connection-pool behavior during failover and failed EXEC paths; mistakes could leak mid-MULTI connections or complete work against the wrong endpoint.

Overview
Reworks experimental MultiDbTransaction so multi-database clients get Redis-like transaction behavior: lazy connection pinning to the active database at acquisition, immediate execution for pre-MULTI / WATCH traffic, in-memory buffering after multi(), and MULTI only sent at exec().

Safety and lifecycle: Rejects WATCH inside MULTI; validates MULTI replies; aborts with JedisException if failover changes the active DB (compensating DISCARD on exec()); marks connections broken when the server may still be in MULTI after failed exec(); discard()/close() avoid pointless server DISCARD when no server transaction was started. Response.of / Response.error are now public for immediate pre-MULTI responses.

Docs add MultiDbTransaction connection and database-switch notes in docs/transactions-multi.md.

Tests: New unit and integration suites for MultiDbTransaction and baseline coverage for MultiDbPipeline (pool return, retry, error paths)—no production changes to pipeline in this diff.

Reviewed by Cursor Bugbot for commit bd3c201. Bugbot is set up for automated code reviews on this repo. Configure here.

- improve multidbtransactin for use cases with watches
@atakavci
atakavci requested review from Copilot and ggivo June 8, 2026 07:48
@atakavci atakavci self-assigned this Jun 8, 2026
@atakavci atakavci added the techdebt Things that can be improved or refactored label Jun 8, 2026
Comment thread src/main/java/redis/clients/jedis/mcf/MultiDbTransaction.java Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR hardens the redis.clients.jedis.mcf (Multi-Cluster Failover) test suites for MultiDbPipeline and MultiDbTransaction, and reworks MultiDbTransaction semantics so pre-MULTI commands (e.g., WATCH/UNWATCH) execute immediately on a lazily acquired connection while post-MULTI commands remain queued until exec().

Changes:

  • Reworked MultiDbTransaction to support immediate execution of pre-MULTI commands, deferred MULTI until exec(), and connection lifecycle management via a held connection.
  • Added/updated unit + integration coverage for pipeline/transaction connection return-to-pool semantics and response-resolution behavior.
  • Exposed Response.of(...) / Response.error(...) publicly to allow constructing already-decoded responses outside the redis.clients.jedis package.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/main/java/redis/clients/jedis/mcf/MultiDbTransaction.java Implements the new pre-MULTI immediate execution + deferred MULTI transaction flow and revised connection lifecycle.
src/main/java/redis/clients/jedis/Response.java Makes Response.of / Response.error public for cross-package use.
src/test/java/redis/clients/jedis/mcf/MultiDbTransactionUnitTest.java Adds unit coverage for the new transaction semantics and connection-release behavior.
src/test/java/redis/clients/jedis/mcf/MultiDbTransactionIntegrationTest.java Adds end-to-end coverage for WATCH → MULTI → EXEC, discard/close paths, and pool release.
src/test/java/redis/clients/jedis/mcf/MultiDbPipelineUnitTest.java Adds unit coverage for retry/ping-failure paths and ensuring borrowed connections are returned/closed.
src/test/java/redis/clients/jedis/mcf/MultiDbPipelineIntegrationTest.java Updates integration assertions around Response.get() behavior pre/post sync() and pool return-to-idle behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/main/java/redis/clients/jedis/mcf/MultiDbTransaction.java
Comment thread src/main/java/redis/clients/jedis/mcf/MultiDbTransaction.java
Comment thread src/main/java/redis/clients/jedis/mcf/MultiDbTransaction.java
Comment thread src/main/java/redis/clients/jedis/mcf/MultiDbTransaction.java Outdated
Comment thread src/main/java/redis/clients/jedis/mcf/MultiDbTransaction.java
Comment thread src/main/java/redis/clients/jedis/mcf/MultiDbTransaction.java Outdated
Comment thread src/main/java/redis/clients/jedis/mcf/MultiDbTransaction.java

@cursor cursor 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.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Reviewed by Cursor Bugbot for commit 3899c36. Configure here.

Comment thread src/main/java/redis/clients/jedis/mcf/MultiDbTransaction.java
Comment thread src/main/java/redis/clients/jedis/mcf/MultiDbTransaction.java Outdated

@ggivo ggivo left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Logic LGTM — left a few syntax notes inline.

  • Database-switch coverage is missing — added it in a stacked PR but might be further extended: atakavci#10. It also disables health checks in these ITs (no failover tested), removing the ~1s per-test
    startup wait in toital ~20s.
  • We should review/update the transaction docs for the MultiDb case (WATCH/UNWATCH flow, deferred MULTI, fail-fast on database switch).
  • PR title is a bit misleading since this change actually introduces an enhancement of the current MultiDBTransaction behavior

Comment thread src/test/java/redis/clients/jedis/mcf/MultiDbPipelineIT.java
Comment thread src/main/java/redis/clients/jedis/mcf/MultiDbTransaction.java
Comment thread src/test/java/redis/clients/jedis/mcf/MultiDbPipelineIT.java
Comment thread src/test/java/redis/clients/jedis/mcf/MultiDbTransactionIntegrationTest.java Outdated
Comment thread src/test/java/redis/clients/jedis/mcf/MultiDbPipelineIntegrationTest.java Outdated
Comment thread src/test/java/redis/clients/jedis/mcf/MultiDbPipelineUnitTest.java Outdated
Comment thread src/test/java/redis/clients/jedis/mcf/MultiDbPipelineUnitTest.java Outdated
Comment thread src/main/java/redis/clients/jedis/mcf/MultiDbTransaction.java Outdated
@atakavci atakavci changed the title Improve test coverage with multidb pipelines and transaction Improve MultiDbTransaction functionality and test coverage Jun 22, 2026

@ggivo ggivo left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM

@atakavci
atakavci merged commit 86b503e into redis:master Jun 29, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement techdebt Things that can be improved or refactored

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants