Skip to content

Comments

fix: Fix out-of-order responses in manual transactions with commands before MULTI#4441

Open
ggivo wants to merge 3 commits intomasterfrom
topic/ggivo/fix-out-of-order-responses-in-tx
Open

fix: Fix out-of-order responses in manual transactions with commands before MULTI#4441
ggivo wants to merge 3 commits intomasterfrom
topic/ggivo/fix-out-of-order-responses-in-tx

Conversation

@ggivo
Copy link
Collaborator

@ggivo ggivo commented Feb 20, 2026

PR Title

Fix out-of-order responses in manual transactions with commands before MULTI

PR Description

Problem

When using manual transactions (doMulti=false) and executing commands before calling multi(), responses were returned out of order. This occurred because all commands were being queued as pipelined commands, regardless of whether the transaction had started.

 
**Example of the issue:**
```java
try (AbstractTransaction tx = jedis.transaction(false)) {
    Response<String> beforeMulti = tx.get("key");  // Queued, not executed
    tx.multi();
    Response<String> inMulti = tx.set("key", "value");  // Queued
    tx.exec();  // Both responses returned here, out of order
    beforeMulti.get();  // Wrong value!
}

Solution

Modified Transaction.appendCommand() to check the inMulti flag:

  • Before multi(): Commands are executed immediately and wrapped in Response.DecodedResponse
  • After multi(): Commands are queued as pipelined commands (existing behavior)

This ensures commands before the transaction get immediate responses while maintaining proper transaction semantics for commands within the transaction block.

Changes

Core Changes

  • Transaction.appendCommand(): Added inMulti flag check to determine execution strategy (execute immediately vs queue in transaction)
  • Transaction.execute(): New private method to execute commands immediately
  • Response.DecodedResponse: New inner class to wrap pre-decoded responses
  • Response.of() / Response.error(): Factory methods for creating decoded responses

Testing

RedisClientTransactionIT.transactionManualWithCommandsBeforeMulti() validates the fix

Backward Compatibility

✅ No breaking changes
✅ Existing transaction behavior unchanged
✅ Only affects manual transactions with commands before multi()

…commands

Fixes out-of-order response issues when using manual transactions
(doMulti=false) mixed with commands executed outside the transaction.

Root Cause:
When commands are executed on a Transaction before multi() is called,
they were being queued as pipelined commands instead of executed
immediately. This caused responses to be out of order when exec()
was later called.

Changes:
- Modified Transaction.appendCommand() to check inMulti flag
- Commands before multi() are now executed immediately via executeCommand()
- Commands after multi() are queued as before (pipelined)
- Added Response.DecodedResponse inner class to wrap immediate results
- Added Response.of() and Response.error() factory methods for
  creating pre-decoded responses

This ensures commands before multi() get immediate responses while
maintaining proper transaction semantics for commands after multi().

Verified by: RedisClientTransactionIT.transactionManualWithCommandsBeforeMulti ✅
@github-actions
Copy link

github-actions bot commented Feb 23, 2026

Test Results

   304 files  + 1     304 suites  +1   12m 11s ⏱️ +27s
10 922 tests +14  10 864 ✅ +830  58 💤  - 816  0 ❌ ±0 
 5 641 runs  +18   5 632 ✅ +300   9 💤  - 282  0 ❌ ±0 

Results for commit e6626c5. ± Comparison against base commit cd75d78.

This pull request removes 10 and adds 24 tests. Note that renamed tests count towards both.
redis.clients.jedis.commands.unified.client.RedisClientMiscellaneousTest[1] ‑ publishInTransaction
redis.clients.jedis.commands.unified.client.RedisClientMiscellaneousTest[1] ‑ transaction
redis.clients.jedis.commands.unified.client.RedisClientMiscellaneousTest[1] ‑ watch
redis.clients.jedis.commands.unified.client.RedisClientMiscellaneousTest[2] ‑ publishInTransaction
redis.clients.jedis.commands.unified.client.RedisClientMiscellaneousTest[2] ‑ transaction
redis.clients.jedis.commands.unified.client.RedisClientMiscellaneousTest[2] ‑ watch
redis.clients.jedis.commands.unified.client.RedisClientMiscellaneousTest[3] ‑ publishInTransaction
redis.clients.jedis.commands.unified.client.RedisClientMiscellaneousTest[3] ‑ transaction
redis.clients.jedis.commands.unified.client.RedisClientMiscellaneousTest[3] ‑ watch
redis.clients.jedis.commands.unified.search.FTHybridCommandsTestBase$SupportedScorersTest ‑ testScorer(Scorer, double, double)
redis.clients.jedis.commands.unified.client.RedisClientTransactionIT$ResponseHandlingIT ‑ afterExecResponseContainsActualResults
redis.clients.jedis.commands.unified.client.RedisClientTransactionIT$ResponseHandlingIT ‑ execReturnsListWithAllResultsInOrder
redis.clients.jedis.commands.unified.client.RedisClientTransactionIT$ResponseHandlingIT ‑ inTransactionResponseThrowsBeforeExec
redis.clients.jedis.commands.unified.client.RedisClientTransactionIT$ResponseHandlingIT ‑ notInTransactionResponsePropagatesException
redis.clients.jedis.commands.unified.client.RedisClientTransactionIT$ResponseHandlingIT ‑ notInTransactionResponseReturnsExpectedValue
redis.clients.jedis.commands.unified.client.RedisClientTransactionIT[1] ‑ publishInTransaction
redis.clients.jedis.commands.unified.client.RedisClientTransactionIT[1] ‑ transaction
redis.clients.jedis.commands.unified.client.RedisClientTransactionIT[1] ‑ transactionManualWithCommandsBeforeMulti
redis.clients.jedis.commands.unified.client.RedisClientTransactionIT[1] ‑ watch
redis.clients.jedis.commands.unified.client.RedisClientTransactionIT[2] ‑ publishInTransaction
…

♻️ This comment has been updated with latest results.

@ggivo ggivo self-assigned this Feb 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Optimistic locking example in documentation uses 2 TCP connections

1 participant