Prevent crash when cgroups_ref is null in streamEntryIsReferenced() after reload (#14276)

This bug was introduced by https://github.com/redis/redis/pull/14130
found by @oranagra

### Summary

Because `s->cgroup_ref` is created at runtime the first time a consumer
group is linked with a message, but it is not released when all
references are removed.

However, after `debug reload` or restart, if the PEL is empty (meaning
no consumer group is referencing any message), `s->cgroup_ref` will not
be recreated.

As a result, when executing XADD or XTRIM with `ACKED` option and
checking whether a message that is being read but has not been ACKed can
be deleted, the cgroup_ref being NULL will cause a crash.

### Code Path
```
xaddCommand -> streamTrim -> streamEntryIsReferenced
```

### Solution

Check if `s->cgroup_ref` is NULL in streamEntryIsReferenced().
This commit is contained in:
debing.sun 2025-08-15 15:15:16 +08:00 committed by GitHub
parent 46a3efa750
commit b9d9d4000b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 28 additions and 0 deletions

View File

@ -2705,6 +2705,7 @@ int streamEntryIsReferenced(stream *s, streamID *id) {
return 1;
/* Check if the message is in any consumer group's PEL */
if (!s->cgroups_ref) return 0;
unsigned char buf[sizeof(streamID)];
streamEncodeID(buf, id);
return raxFind(s->cgroups_ref, buf, sizeof(streamID), NULL);

View File

@ -244,6 +244,33 @@ start_server {
assert {[r XLEN mystream] == 1} ;# Successfully trimmed to 1 entries
}
test {XADD with ACKED option doesn't crash after DEBUG RELOAD} {
r DEL mystream
r XADD mystream 1-0 f v
# Create a consumer group and read one message
r XGROUP CREATE mystream mygroup 0
set records [r XREADGROUP GROUP mygroup consumer1 COUNT 1 STREAMS mystream >]
assert_equal [lindex [r XPENDING mystream mygroup] 0] 1
# After reload, the reference relationship between consumer groups and messages
# is correctly rebuilt, so the previously read but unacked message still cannot be deleted.
r DEBUG RELOAD
r XADD mystream MAXLEN = 1 ACKED 2-0 f v
assert_equal [r XLEN mystream] 2
# Acknowledge the read message so the PEL becomes empty
r XACK mystream mygroup [lindex [lindex [lindex [lindex $records 0] 1] 0] 0]
assert {[lindex [r XPENDING mystream mygroup] 0] == 0}
# After reload, since PEL is empty, no cgroup references will be recreated.
r DEBUG RELOAD
# ACKED option should work correctly even without cgroup references.
r XADD mystream MAXLEN = 1 ACKED 3-0 f v
assert_equal [r XLEN mystream] 2
} {} {needs:debug}
test {XADD with MAXLEN option and DELREF option} {
r DEL mystream
r XADD mystream 1-0 f v