fix(stream): decrement pending_number when XAUTOCLAIM removes deleted PEL entries - #3578
Conversation
|
Hi @wengsht, Thank you for your pull request. Please review our Contributing Guide. Please make sure you understand your changes and explain your reasoning in this pull request. Low-quality pull requests may be closed. |
383cfa8 to
ce83a6e
Compare
… entries When XAUTOCLAIM meets a PEL entry whose stream entry was trimmed or XDEL'd, it deletes the PEL entry but never decrements the group's or the owning consumer's pending_number. The counters drift upward on every such sweep and, being unsigned, eventually underflow-wrap on later decrements, after which XINFO GROUPS / XPENDING report garbage. Decrement both when removing a deleted entry, matching DeletePelEntries (XACK). A claim only moves ownership within the group, so it is net-zero for the group count; only deleted entries reduce it. Subtractions are saturating so a counter that has already drifted cannot wrap. Signed-off-by: Shitao Weng <shitao@tbean.ai>
ce83a6e to
809bc37
Compare
|
@LindaSummer could you help approve the CI run? This fixes pending_number drifting upward when XAUTOCLAIM sweeps PEL entries whose stream body was already trimmed/XDEL'd — the counter is an unclamped uint64, so in production it eventually wraps past INT64_MAX and breaks any client that decodes XINFO GROUPS pending as a signed int. The decrement mirrors the existing logic in DeletePelEntries. Repro added as a gocase test. |
There was a problem hiding this comment.
🟡 Not ready to approve
A newly added test relies on the server default XREADGROUP COUNT behavior, which can make the test nondeterministic unless COUNT is set explicitly.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Pull request overview
Fixes two stream consumer-group counters that could underflow-wrap and be serialized as RESP integers larger than INT64_MAX, which can break clients that decode integer replies as signed 64-bit. The changes are in the stream implementation (XAUTOCLAIM PEL sweeping and XINFO GROUPS lag computation) and are covered by new Go unit tests.
Changes:
- Fix
XAUTOCLAIMto decrement per-consumer and per-grouppending_numberwhen sweeping dangling PEL entries (trimmed/XDEL’d entries), using saturating subtraction to avoid wrap. - Fix
XINFO GROUPSlag computation to avoid unsigned underflow whenentries_read > entries_added, falling back to the existing invalid-lag sentinel (UINT64_MAX). - Add Go tests covering both behaviors to ensure replies remain decodable and counters return to sane values.
File summaries
| File | Description |
|---|---|
| tests/gocase/unit/type/stream/stream_test.go | Adds regression tests for XAUTOCLAIM pending decrement on swept PEL entries and for XINFO GROUPS lag underflow/overflow safety. |
| src/types/redis_stream.cc | Adjusts XAUTOCLAIM bookkeeping for pending counters (with saturation) and guards lag subtraction to prevent underflow wrap. |
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 1
- Review effort level: Lite
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
de3bb5f to
8d8ad67
Compare
|
Hi @wengsht , Thanks for your effort in this problem. If you want to run a full CI by yourself, you could create a PR in your forked repo and enable its github action. |
Thanks for the tip! |
LindaSummer
left a comment
There was a problem hiding this comment.
Hi @wengsht ,
Thanks very much for your effort for these two issues.
I have several suggestions for this patch.
- It appears that we solved two independent issues in one patch. This is not good practice since we need to cherry-pick in the release process, and it violates SRP (Single Responsibility Principle). We'd better create two patches for these two issues.
- The fix for the
entries-readhas a case not aligning with Redis's behavior.
For the XAUTOCLAIM fix, it generally looks good to me.
Have a wonderful day!
…test Make the pending-count precondition independent of the XREADGROUP COUNT default, per review feedback. COUNT is >= the number of entries so all three are still delivered in one call. Signed-off-by: Shitao Weng <shitao@tbean.ai>
Reflow the saturating pending_number ternary per clang-format (the CI "Check with clang-format" lane). No behavior change. Signed-off-by: Shitao Weng <shitao@tbean.ai>
b140b8b to
cd37ad1
Compare
| current_consumer_metadata.pending_number = current_consumer_metadata.pending_number >= it->second | ||
| ? current_consumer_metadata.pending_number - it->second | ||
| : 0; | ||
| consumer_pending_decrements.erase(it); |
There was a problem hiding this comment.
Maybe we could make current consumer data stored outside of the consumer_pending_decrements to reduce erase of a map.
Just a nitpick, non-mandatory.
There was a problem hiding this comment.
thanks for the review again!
updated.
…f the map Per review: the current consumer never appears in claimed_consumer_entity_count (entries are only claimed from other consumers), so track its own deleted-entry decrement in a scalar and let consumer_pending_decrements hold only the other consumers. This drops the find-and-erase on the map. No behavior change.
LindaSummer
left a comment
There was a problem hiding this comment.
🚀 LGTM
Thanks very much for your effort!
…, add tests Review follow-ups on top of the rebase onto unstable (post-apache#3578): - CheckLagValid: guard entries_read <= entries_added before the unsigned subtraction. A clamp on write (CREATE/SETID) does not stop ReadGroup from incrementing entries_read past entries_added on a subsequent XREADGROUP, which would re-underflow lag to ~2^64 and break XINFO GROUPS decoding again. Falling through to the estimate path keeps the reply decodable. (Copilot review.) - ClampEntriesRead: guard entries_read >= 0 rather than != -1, so any negative value is handled before the uint64 cast. (LindaSummer review.) - Merge the size==0 (all-entries-deleted) lag=0 case into the entries_added==0 branch. (LindaSummer review.) - Tests: add the read-after-clamp regression (XREADGROUP then XINFO GROUPS stays decodable, lag 0) and an emptied-stream lag=0 case. Restore the apache#3578 XAUTOCLAIM pending_number test dropped during conflict resolution. (Copilot review.) Ran clang-format-18 and gofmt.
The group's and the owning consumer's
pending_numbercan be serialized as a RESP integer larger thanINT64_MAX, which breaks any client that decodes integer replies as signed 64-bit — the reply fails to parse and the connection is dropped. Hit in production.XAUTOCLAIM
pending_numberdriftWhen XAUTOCLAIM meets a PEL entry whose stream entry was trimmed or XDEL'd, it deletes the PEL entry but never decrements the group's or the owning consumer's
pending_number. The counts drift upward on every such sweep and, being unsigned, eventually underflow-wrap on a later decrement — after whichXINFO GROUPSandXPENDINGreport garbage.The fix mirrors
DeletePelEntries(XACK): when a deleted entry is removed from the PEL, decrement the owning consumer and the group. A claim only moves ownership within the group (net-zero for the group count), so only deleted entries reduce it. Subtractions are saturating so a counter that has already drifted can't wrap.Test: read entries into a consumer, XDEL them, XAUTOCLAIM, then check the group and consumer pending counts return to 0. The existing XDEL autoclaim tests only asserted the returned deleted-id list.
AI assistance: diagnosis and drafting were done with AI help; I've reviewed the changes and tests and understand the behavior.