Repository navigation
Improve MultiDbTransaction functionality and test coverage - #4561
Conversation
- improve multidbtransactin for use cases with watches
There was a problem hiding this comment.
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
MultiDbTransactionto support immediate execution of pre-MULTIcommands, deferredMULTIuntilexec(), 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 theredis.clients.jedispackage.
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.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Reviewed by Cursor Bugbot for commit 3899c36. Configure here.
ggivo
left a comment
There was a problem hiding this comment.
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

Summary
Improves the behavior and functionality of
MultiDbTransactionso it matches the semantics users expect from a Redis transaction, and adds the test coverage to back it.WATCH,UNWATCHand any pre-MULTIcommands 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-MULTIcommands stay buffered untilexec(). This enables the canonicalwatch → read → multi → write → execflow through a single transaction handle.What changed in
MultiDbTransactiondoMulti=true, deferred untilexec(). WithdoMulti=false, acquired on the first command that needs the server. The same connection is reused for the whole lifecycle.appendCommand:Response.of/Response.errorfor immediate commands, bufferedResponses for queued ones.MULTIreply validated by byte-array equality againstOK; a non-OKreply now surfaces asJedisDataExceptioninstead of being silently treated as success.watch(...)insideMULTIis rejected withIllegalStateException.unwatch()insideMULTIis buffered like any other queued command and returnsnull(its reply arrives viaexec()).discard()contacts the server only when needed — sendsUNWATCHif a connection was already held forWATCH, otherwise clears local state only. Never sendsDISCARDfor a transaction that was never started server-side.MULTIcommand and the pre-EXECcheck both throwJedisException("Active database has changed since transaction started"). Onexec()a compensatingDISCARDis sent on the original connection before aborting.exec()fails leaving the server inMULTI(e.g.EXECABORT, or the compensatingDISCARDalso 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.errorpromoted topublicsoMultiDbTransaction(in a different package) can build already-decoded responses for pre-MULTIcommands instead of mutating internal state.Tests
MultiDbPipelineandMultiDbTransactionpreviously had no dedicated unit or integration suites. This PR introduces both.MultiDbTransaction— new coverage for the reworked behaviorUnit (mocked connection + pool, no I/O):
MULTIviaexecuteCommand(CommandObject)returnsOKbyte[],getMany(...)returnsQUEUEDbyte[]s,EXECformats replies through each bufferedResponse.null-reply (WATCH-abort) path.WATCH → close → UNWATCHon a single eagerly-acquired connection.exec()/discard()withoutMULTI.discard()/close()never touches the pool.Integration (real Redis):
discard()rolls them back.close()while inMULTIissues a discard.exec()anddiscard()return the connection to the active pool.WATCH → MULTI → EXECflow.exec()does not poison the pooled connection on subsequent reuse.MultiDbPipeline— baseline coverage (no production change)Unit:
sendCommandorgetManythrows mid-flush.close()triggerssync(); ping-failure propagation.Integration:
sync()/close().sync()on the same pipeline.Response.get()contract —IllegalStateExceptionbeforesync(), 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
MultiDbTransactionso multi-database clients get Redis-like transaction behavior: lazy connection pinning to the active database at acquisition, immediate execution for pre-MULTI/WATCHtraffic, in-memory buffering aftermulti(), andMULTIonly sent atexec().Safety and lifecycle: Rejects
WATCHinsideMULTI; validatesMULTIreplies; aborts withJedisExceptionif failover changes the active DB (compensatingDISCARDonexec()); marks connections broken when the server may still be inMULTIafter failedexec();discard()/close()avoid pointless serverDISCARDwhen no server transaction was started.Response.of/Response.errorare now public for immediate pre-MULTIresponses.Docs add
MultiDbTransactionconnection and database-switch notes indocs/transactions-multi.md.Tests: New unit and integration suites for
MultiDbTransactionand baseline coverage forMultiDbPipeline(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.