Fix: incorrect list commands - #220
Conversation
|
@Aryex I will check this tomorrow. Can you please not remove rpoplpush and brpoplpush as we still support these in valkey right ? |
|
@Sasidharan3094 We could support then as facades as recommended by both Valkey and Redis. Example: Please note that RPOPLPUSH and BRPOPLPUSH are not implemented at the core hence this would be the way to go. |
blpop, brpop, blmove, rpoplpush and brpoplpush were all non-functional.
* _bpop called send_blocking_command, a helper that does not exist
anywhere in the repo, so blpop/brpop raised NoMethodError on every
call. Route them through send_command instead; glide-core handles the
blocking wait natively, so no separate dispatch path is needed.
* blpop passed the Symbol :blpop as the request type where an Integer
RequestType constant is required.
* blmove prepended a stray :blmove token to argv, so the server rejected
every call with "wrong number of arguments".
* rpoplpush/brpoplpush dispatched RequestType::RPOPLPUSH/BRPOPLPUSH, for
which glide-core has no get_command mapping arm, so calls failed with
"Couldn't fetch command type- ClientError".
Rather than remove the two deprecated commands, implement them as facades.
Valkey defines RPOPLPUSH as precisely LMOVE ... RIGHT LEFT, so this is
fixed-argument currying, not a translation:
RPOPLPUSH src dst == LMOVE src dst RIGHT LEFT
BRPOPLPUSH src dst timeout == BLMOVE src dst RIGHT LEFT timeout
As one-line facades they inherit lmove/blmove's where normalization,
timeout validation and argv construction, so they cannot drift from them.
redis-rb callers keep working, and both remain documented as deprecated in
favour of lmove/blmove. The unusable RequestType::RPOPLPUSH/BRPOPLPUSH
constants are removed; nothing referenced them.
Make the blpop/brpop timeout an explicit keyword parameter. Both took
*args and inferred the timeout by sniffing for a trailing options Hash, a
port of redis-rb's signature; that indirection is where these bugs came
from, since an undeclared parameter cannot be validated at the boundary
and a surviving Hash becomes a key while the timeout falls back to 0 (block
forever). The positional timeout that *args existed to support was
deprecated in redis-rb 4.8.0 and removed in 5.0.0, so the splat's only
remaining job is variadic keys:
def blpop(*keys, is_left: true, timeout: 0)
Every documented redis-rb 5.x call shape still works -- single key, Array,
splat, mixed, and implicit timeout -- and the contract now matches the
Python client (keys checked in order, [key, value] return, 0 blocks
indefinitely). The one behavioral change is an explicit positional Hash,
blpop("k", {timeout: 1}), which is the form redis-rb 5.0 removed and is
used nowhere in this repo. Also drop the unreachable &blk from _bpop:
neither blpop nor brpop declared or forwarded a block, so it was always nil.
Extract _validate_blocking_timeout so blmove validates its timeout the
same way blpop/brpop do, and correct the YARD on blpop/brpop/blmove/
brpoplpush, all of which documented a `@param [Hash] options` that no
longer exists. The @example lines mattered most: they showed
`:timeout => 5`, the one now-broken form, so copy-pasting them would have
produced a block-forever call.
Add tests for blpop, brpop, blmove, rpoplpush and brpoplpush, which
previously had zero coverage. The load-bearing ones for the facades are the
currying guards: they capture request type and argv for the facade and for
the equivalent explicit lmove/blmove call and assert both are identical, so
a facade that translated rather than curried would fail. This needs
capture_blocking_call alongside capture_blocking_argv, which discards the
request type -- argv alone cannot prove "same command". A dropped timeout
cannot be caught by a timing assertion, since the blocking FFI call
releases the GVL and cannot be interrupted by Timeout.timeout, so the
timeout guards assert on built argv and fail in milliseconds rather than
hanging the suite.
Verified against a standalone server: 27 tests covering these commands,
85 assertions, 0 failures, 0 errors, 0 skips. Full commands_test.rb is
unchanged from baseline (1 failure, 4 errors, all pre-existing: 4 SSL tests
with no TLS server, and test_library_name_is_set from a stale local
libglide_ffi). rubocop clean on all changed files.
Signed-off-by: Alex Le <alex.le@improving.com>
61032bb to
bed14fb
Compare
blpop, brpop, blmove, rpoplpush and brpoplpush
blpop, brpop, blmove, rpoplpush and brpoplpush
jamesx-improving
left a comment
There was a problem hiding this comment.
Verdict: Approve
LGTM. The three fixes and the two facades all check out against Valkey's semantics, and the argv/currying guards in the tests will catch a regression on any of them.
Summary
The PR fixes five non-functional list commands (blpop, brpop, blmove, rpoplpush, brpoplpush) by:
- Removing a call to a non-existent
send_blocking_commandhelper - Dropping a stray leading token from
blmove's argv - Expressing
rpoplpush/brpoplpushas fixed-argument facades over the semantically-equivalentlmove/blmove
Diff, tests, and PR description are internally consistent. I verified the semantic equivalence and did not find any correctness or security issues worth reporting.
Findings: 0 high-confidence correctness or security issues across the 4 files reviewed.
Verification notes (what I actually checked)
send_blocking_commandis gone from the repo —grep -rn "send_blocking_command"acrosslib/andtest/returns no hits, and both call sites inlist_commands.rbhave been replaced withsend_command.- The two removed
RequestTypeconstants (RPOPLPUSH = 820,BRPOPLPUSH = 805) have no remaining callers — grep forRequestType::RPOPLPUSH/BRPOPLPUSHand case-variants across the repo returns nothing. Removing them is safe. - The
rpoplpush => LMOVE RIGHT LEFTequivalence is correct against Valkey semantics, including the source == destination "rotate the tail to the head" edge case, so the facade is safe. Same forbrpoplpush => BLMOVE RIGHT LEFTwith timeout preserved. _bpopnow dispatchesBLPOP/BRPOPviasend_commandwith argv[*keys, timeout].keys.flatten(1)handles both splat and Array forms; deeper nesting is additionally normalised bybuild_command_args's own flat_map inlib/valkey.rb:630.- The one intentional break called out in the PR body — passing an explicit positional Hash
blpop("k", { timeout: 1 })— matches the redis-rb 5.0.0 removal and is not used anywhere in this repo. Nothing to flag. - Test coverage is thorough: argv-shape guards via
stub(:send_command), currying guards assertingrpoplpush/brpoplpushdispatch the exact same RequestType and argv aslmove/blmove, a real-server timeout-elapsed check forbrpop, and float-timeout survival tests. The tests would catch a regression on any of the four fixes.
Low-confidence observations (not bugs, just author-glance notes)
_validate_blocking_timeoutaccepts negative numeric timeouts and forwards them to the server, which then rejects them at RESP level. Fine as a client-side choice (server enforces the range) — noting it only in case the author wants a friendlier client-side error.test_brpopandtest_blpopeach issue one call with no explicit timeout after anrpush. If the server ever fails to pop for any reason the test blocks indefinitely rather than failing fast. Not a correctness issue in the PR; just a test-brittleness note.
currantw
left a comment
There was a problem hiding this comment.
Some review comments about deprecation and Redis vs Valkey. Will take another look more fully once addressed.
| # @note This command comes in place of the now deprecated BRPOPLPUSH. | ||
| # Doing BLMOVE RIGHT LEFT is equivalent. |
There was a problem hiding this comment.
Why is BRPOPLPUSH deprecated? It doesn't appear to be marked as such for Valkey: https://valkey.io/commands/brpoplpush/
There was a problem hiding this comment.
It was deprecated in Redis. Valkey started as a fork of Redis so I'm guessing it is a hold over.
https://redis.io/docs/latest/commands/brpoplpush/
Both Valkey and Redis suggested to use LMOVE/BLMOVE as alternative so this simply implements that.
There was a problem hiding this comment.
Looking at this Valkey issue, it seems like the deprecation decisions may have diverged on Valkey and Redis, and that these commands may be "undeprecated" in Valkey. We definitely need to support both, but I think that, if connected to a Valkey server, we should execute that corresponding command?
There was a problem hiding this comment.
Hmm I guess that clear things up. However BRPOPLPUSH/RPOPLPUSH is currently not supported at the core. For Ruby GA, we can route them to LMOVE/BLMOVE until they are implemented at the core.
There was a problem hiding this comment.
That makes sense. In terms of making this trackable, would it then make sense to:
- Raise an issue to add support for them in the core
- Raise an issue to add support for them in Ruby
- Link the Ruby issues with TODOs here
The core issue should make sure to link to the Ruby issue, and prompt whoever completes it to raise corresponding issues for the other independent clients (i.e. C# and PHP at the moment).
Signed-off-by: Alex Le <alex.le@improving.com>
currantw
left a comment
There was a problem hiding this comment.
Thanks for adding all those TODOs for me! ✅
Summary
blpop,brpop,blmove,rpoplpushandbrpoplpushwere all non-functional. This PR fixes all five. No breaking changes.Issue link
Related #216
What was broken
blpop/brpop_bpopcalledsend_blocking_command, a helper that does not exist anywhere in the repo →NoMethodErroron every call.blpopalso passed the Symbol:blpopwhere an IntegerRequestTypeis required.blmove:blmovetoken to argv → server rejected every call withwrong number of arguments.rpoplpush/brpoplpushRequestType::RPOPLPUSH/BRPOPLPUSH, for which glide-core has noget_commandmapping arm →Couldn't fetch command type- ClientError.Changes
Blocking dispatch.
blpop/brpopnow go through the ordinarysend_command— glide-core handles the blocking wait natively, so no separate dispatch path is needed.rpoplpush/brpoplpushas facades overlmove/blmove. Valkey definesRPOPLPUSHas preciselyLMOVE ... RIGHT LEFT, so the missing dispatch path is recoverable by fixed-argument currying rather than by dropping the API:As one-line facades they inherit
lmove/blmove's where-normalization, timeout validation and argv construction, so they cannot drift. Both remain documented as deprecated (Redis 6.2) in favour oflmove/blmove. The unusableRequestType::RPOPLPUSH/BRPOPLPUSHconstants are removed — nothing referenced them.Explicit
timeoutkeyword onblpop/brpop. Both took*argsand inferred the timeout by sniffing for a trailing options Hash. That indirection is where the bugs came from: an undeclared parameter cannot be validated at the boundary, and a Hash that survives the sniff becomes a key while the timeout silently falls back to0— block forever. The positional timeout that*argsexisted to support was deprecated in redis-rb 4.8.0 and removed in 5.0.0, so the splat's only remaining job is variadic keys:Every documented redis-rb 5.x call shape still works — single key, Array, splat, mixed, implicit timeout — and the contract now matches the Python client (keys checked in order,
[key, value]return,0blocks indefinitely). Also dropped the unreachable&blkfrom_bpop: neither method declared or forwarded a block, so it was alwaysnil.Refactor. Extracted
_validate_blocking_timeoutsoblmovevalidates its timeout the same wayblpop/brpopdo — new validation forblmove, which previously had none.Docs. Corrected YARD on
blpop/brpop/blmove/brpoplpush, all of which documented a@param [Hash] optionsthat no longer exists. The@examplelines mattered most: they showed:timeout => 5, which is the one form this PR changes, so copy-pasting them would have produced a block-forever call.Compatibility
One behavioral change: an explicit positional Hash,
blpop("k", { timeout: 1 }), previously worked via the Hash sniff and now treats the Hash as a key. This is the form redis-rb removed in 5.0.0; it is used nowhere in this repo, andblpop("k", timeout: 1)(keywords) is unaffected. All other forms are byte-identical — verified by diffing built argv across 13 call shapes before and after.Checklist
bundle exec rubocop) and pass.release-1.0