Skip to content

Enhancing multi/pipeline errors but defaults to redis-rb for parity - #299

Merged
Aryex merged 5 commits into
valkey-io:mainfrom
Sasidharan3094:multi-pipeline-err-handling
Sep 9, 2026
Merged

Aryex merged 5 commits into
valkey-io:mainfrom
Sasidharan3094:multi-pipeline-err-handling

Conversation

@Sasidharan3094

@Sasidharan3094 Sasidharan3094 commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

Summary

pipelined and the block form of multi raised Valkey::CommandError and discarded the entire
reply array whenever a queued command failed at runtime (e.g. INCR on a non-integer) - even
though the server had already committed the other queued commands. This broke per-command
retry/idempotency logic, since callers had no way to see which commands succeeded.

This PR adds an opt-in exception: false mode to both pipelined and multi that returns the
reply array with a Valkey::CommandError embedded at the failing command's slot instead of
raising, matching the server's "no rollback on a runtime error" semantics. The default
behavior of both methods is left as exception: true (raise, array discarded) to stay in parity
with redis-rb 5.x, which was verified to behave identically to valkey-glide-ruby's pre-fix
default for both pipelined and multi (redis-rb's multi has no opt-out at all).

Also fixes two related bugs found while investigating:

  • incrbyfloat / geodist returned a String instead of a Float when called inside
    multi/exec's imperative (non-block) form, unlike a direct call.
  • A runtime error embedded in that imperative form's EXEC reply could be silently coerced to
    true by a boolean-typed command's reply reconversion (Utils::Boolify), hiding the error.

Issue link

Closes #260

Features / Changes

  • pipelined's existing exception: kwarg is unchanged in shape; multi's block form gains a
    matching exception: kwarg (default true) - a valkey-glide-ruby-specific extension, since
    redis-rb's multi has no such option.
  • exception: false on either method returns the reply Array with Valkey::CommandError
    embedded at the failing command's position; other commands' replies (and their Futures) stay
    reachable.
  • Added Valkey::ExecAbortError < Valkey::CommandError, raised specifically for a genuine
    queue-time abort (RequestErrorType::EXECABORT) - so callers can distinguish "nothing ran, safe
    to retry" from a mid-EXEC runtime error, matching the Python/Node/Java/Go GLIDE clients.
  • multi's exception: kwarg only affects the block form; passing it to the imperative
    multi (no block) / #exec pair now raises ArgumentError instead of silently being ignored,
    since that path dispatches a single literal EXEC command with no equivalent raise/embed
    control (redis-rb has no imperative multi/exec form at all to compare against - calling
    multi without a block raises LocalJumpError there).
  • incrbyfloat (string_commands.rb) and geodist (geo_commands.rb) now apply
    Utils::Floatify, matching the existing convention used by hincrbyfloat/zincrby/zscore.
  • reconvert_queued_replies (imperative exec's reply reconversion) now skips re-coercing an
    already-embedded CommandError, mirroring the existing guard in send_batch_commands.

Testing

  • Reproduced the reported behavior against unmodified main on a local standalone server
    (127.0.0.1:6379): both pipelined and multi raised and discarded the array, while a
    follow-up GET confirmed the successful command had committed server-side.
  • Cross-checked against real redis-rb 5.4.1 (same server, same scenario) to confirm this fix's
    defaults match redis-rb's own pipelined/multi defaults exactly, with exception: false as
    the only divergence (an explicit, deliberate opt-in enhancement).
  • Added/updated tests:
    • test/lint/string_commands.rb - default (exception: true) raises; exception: false
      returns the array with the error embedded.
    • test/lint/transaction_commands.rb - same coverage for multi's block form, plus float
      coercion parity between multi/exec (block and imperative) and direct calls, plus
      multi(exception: ...) without a block raising ArgumentError.
    • test/unit/errors_test.rb (new) - ExecAbortError class hierarchy.
  • bundle exec rubocop lib test - clean.
  • bundle exec rake test:unit - 123 tests, 0 failures.
  • Targeted test/integration/standalone/commands_test.rb run (multi/pipelined/geodist/incrbyfloat
    filter) - 49 tests, 0 failures, 5 unrelated skips.

Checklist

Before submitting the PR make sure the following are checked:

  • This Pull Request is related to one issue.
  • Commit message has a detailed description of what changed and why.
  • Tests are added or updated.
  • Documentation is updated (if applicable).
  • Linters have been run (bundle exec rubocop) and pass.
  • Destination branch is correct - main

@jamesx-improving jamesx-improving 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.

Reviewed the multi/pipelined error-handling changes, the ExecAbortError split, and the incrbyfloat/geodist float-coercion fixes — the CommandError-embedding guards are consistent across both the batch and imperative-exec paths, and ExecAbortError < CommandError keeps all existing rescues working. LGTM.

@Sasidharan3094
Sasidharan3094 force-pushed the multi-pipeline-err-handling branch from 9104287 to efa098c Compare August 30, 2026 13:51
Comment thread lib/valkey/commands/geo_commands.rb
Comment thread lib/valkey/commands/string_commands.rb
Comment thread lib/valkey/commands/transaction_commands.rb Outdated
Comment thread test/unit/errors_test.rb Outdated
Comment thread test/lint/transaction_commands.rb Outdated
Comment thread test/lint/transaction_commands.rb Outdated
Comment thread test/lint/transaction_commands.rb Outdated
Comment thread test/lint/transaction_commands.rb Outdated
Comment thread test/lint/string_commands.rb
Comment thread lib/valkey.rb Outdated
@Sasidharan3094

Copy link
Copy Markdown
Collaborator Author

@Aryex I will address the review comments tomorrow

@Sasidharan3094
Sasidharan3094 requested a review from Aryex September 3, 2026 06:56
Signed-off-by: Sasidharan Gopal <sasidharan.gopal94@gmail.com>
Signed-off-by: Sasidharan Gopal <sasidharan.gopal94@gmail.com>
Signed-off-by: Sasidharan Gopal <sasidharan.gopal94@gmail.com>
@Sasidharan3094
Sasidharan3094 force-pushed the multi-pipeline-err-handling branch from a4b1fe7 to b865142 Compare September 3, 2026 06:59
@Sasidharan3094

Copy link
Copy Markdown
Collaborator Author

@Aryex I addressed the comments, can you review again

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

Reviewed up to f7a88c2

Just a few comments. Other than that lgtm.

Comment thread lib/valkey/errors.rb Outdated
Comment thread test/unit/errors_test.rb Outdated
Comment thread CHANGELOG.md Outdated
Signed-off-by: Sasidharan Gopal <sasidharan.gopal94@gmail.com>
@Sasidharan3094
Sasidharan3094 requested a review from Aryex September 9, 2026 06:04
@Sasidharan3094

Copy link
Copy Markdown
Collaborator Author

@Aryex Fixed review comments.

@Aryex
Aryex merged commit 6a6948f into valkey-io:main Sep 9, 2026
18 checks passed
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.

pipelined / multi drop the entire reply array when any queued command errors

3 participants