fix(scheduler): skip acknowledge and delete when the consumer batch is empty - #13770
Open
abhisheksharma2411 wants to merge 1 commit into
Open
fix(scheduler): skip acknowledge and delete when the consumer batch is empty#13770abhisheksharma2411 wants to merge 1 commit into
abhisheksharma2411 wants to merge 1 commit into
Conversation
…s empty `get_batches` guarded the empty-stream case on `RedisError::StreamEmptyOrNotAvailable`. `XREADGROUP` reports "nothing undelivered" as an empty reply rather than an error, so that branch never runs for the consumer's read, and execution reached `stream_acknowledge_entries` with no entry IDs. `XACK key group` with no IDs is a syntax error, so every poll of an idle stream logged an ERROR pair — from the Redis layer and from the scheduler wrapper above it. Return once the collected entry IDs are known to be empty, which holds whichever way a backend chooses to report an empty read. The existing error branch is kept as the outer guard for readers that do report it as an error, `XREAD` on an absent stream among them. `XDEL` rejects an empty ID list for the same reason, so the delete that follows was equally affected; it was simply masked by the acknowledge failing first. Move `metrics::BATCHES_CONSUMED` below the check so that an empty poll is no longer counted as a consumed batch. The two backends genuinely differ here, so neither guard is redundant: redis-rs returns an empty reply and fred returns `StreamEmptyOrNotAvailable`, which makes this redis-rs-only and makes the existing branch the live path for fred. Add a redis_interface test pinning that, asserting the contract both satisfy — an exhausted read reports no work, by either shape, and never reports entries that are not there.
Changed Files
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Type of Change
Description
get_batchesdecides "the stream is empty, there is nothing to do" fromRedisError::StreamEmptyOrNotAvailable.XREADGROUPdoes not report an exhausted stream that way — it returns an empty reply — so that branch never runs for the consumer's read. Execution falls through tostream_acknowledge_entrieswith an emptyentry_ids, andXACK key groupwith no IDs is a syntax error. Every poll of an idle stream logs anERRORpair, one from the Redis layer and one from the scheduler wrapper above it.The fix returns once
entry_idsis known to be empty. That holds whichever way a backend reports an empty read, so it is not sensitive to howredis-rsandfredeach choose to surface it.Three things worth calling out:
1.
XDELis affected the same way, which the issue did not cover.stream_delete_entriesruns on the same emptyentry_ids, andXDELrejects an empty ID list for exactly the same reason. It is invisible today only because the acknowledge fails first and returns via?. A fix that madestream_acknowledge_entriestolerate an empty list would have moved the error one line down rather than removing it — which is part of why the guard belongs inget_batches.2. The existing error branch is kept rather than removed — and the two backends genuinely differ. The issue asks: "Worth confirming whether the fred backend reports an empty stream the same way. If it does, the guard is dead for both backends." I ran it. It does not, so the guard is not dead:
XREADGROUPreturnsredis-rsOk, empty replyfredErr(StreamEmptyOrNotAvailable)So this bug is redis-rs-only, and removing the error branch would have broken the fred path. Keeping both is not belt-and-braces — each is load-bearing for one backend. That is also why the fix guards on
entry_ids.is_empty()rather than on which error came back: it is the one condition that holds either way.I found this the hard way. My first version of the test asserted the
redis-rsshape and went red on fred, which is how the divergence surfaced; the test now asserts the union both backends satisfy.3.
metrics::BATCHES_CONSUMEDmoves below the check. It was incremented immediately after the read, so every empty poll counted as a consumed batch — on an idle deployment that is the entire series. A side effect worth noting explicitly: a batch that fails to deserialize now returns before the increment too, where previously it was counted. That seems right for a counter named "batches consumed", but say the word if you would rather keep the read-attempt semantics and I will split it out.Motivation and Context
Fixes #13642.
The reporter's analysis was accurate and I have not had to correct any of it — this PR is essentially their suggested fix, plus the
XDELhalf and the metric.How did you test it?
Reproduced the mechanism against Redis 7 first, so the failure is observed rather than inferred:
Step 3 is the crux: the empty read is an empty reply, not an error. Step 5 is the masked second failure.
Added a test —
test_stream_read_with_options_xreadgroup_emptyincrates/redis_interface/src/test.rs. It builds the realistic case (append, create the group, drain it) rather than reading a stream that was never written to, and asserts the contract both backends actually satisfy: an exhausted read reports no work — as an empty reply or asStreamEmptyOrNotAvailable— and never reports entries that are not there.To be straight about what it does and does not do: it passes both before and after this change, because it covers
redis_interface, which the fix does not touch. It pins the divergence in the table above so that neither backend can drift into the other's shape unnoticed — the thing that would silently reintroduce this class of bug. The reproduction above is the evidence for the bug itself.Placed there because that suite already requires a live Redis and runs against both backends;
crates/schedulerhas no Redis-backed test harness, and I did not want to makecargo test -p schedulernewly dependent on one. Happy to add one if you would preferget_batchescovered directly — that would be the test that goes red without the guard.Verified against a live Redis 7:
cargo test -p redis_interface(redis-rs)cargo test -p redis_interface --no-default-features --features fredjust clippycargo +nightly fmt --allThe one fred failure is
test_scan_returns_matching_keys, which is pre-existing and unrelated — I confirmed it by stashing my changes and re-running it onmain, where it fails identically. It uses aunique_test_id()-scoped pattern, so nothing I added can reach it. Flagging it since it looks like a real fred/redis-rs behavioural difference inscanrather than a flake, and it may be worth its own issue; happy to open one if that would be useful.Note that
cargo testdoes not run in CI, so the new test will not appear in the checks on this PR — it is a developer-run suite, per the module docs intest.rs.Checklist
cargo +nightly fmt --allcargo clippy