Fix memory leak in ZDIFF algorithm 2 on early exit (#14932)
Some checks failed
CI / test-ubuntu-latest (push) Has been cancelled
CI / test-sanitizer-address (push) Has been cancelled
CI / build-debian-old (push) Has been cancelled
CI / build-macos-latest (push) Has been cancelled
CI / build-32bit (push) Has been cancelled
CI / build-libc-malloc (push) Has been cancelled
CI / build-centos-jemalloc (push) Has been cancelled
CI / build-old-chain-jemalloc (push) Has been cancelled
Codecov / code-coverage (push) Has been cancelled
External Server Tests / test-external-standalone (push) Has been cancelled
External Server Tests / test-external-cluster (push) Has been cancelled
External Server Tests / test-external-nodebug (push) Has been cancelled
Spellcheck / Spellcheck (push) Has been cancelled

## Problem

`zdiffAlgorithm2()` can break out early once the destination cardinality
reaches zero. In that path, a temporary SDS created by
`zuiSdsFromValue()` is left dirty and never released, because the
cleanup normally happens on the next `zuiNext()` call which is skipped
due to the early `break`.

`zuiClearIterator()` called after the loop does **not** clean up dirty
values — only `zuiNext()` or explicit `zuiDiscardDirtyValue()` does.

## Fix

Add `zuiDiscardDirtyValue(&zval)` before the early `break` to ensure the
temporary SDS is freed on all exit paths.
This commit is contained in:
Guimu 2026-03-27 22:09:57 +08:00 committed by GitHub
parent 8e89e0b89f
commit 2ba0194fbe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 16 additions and 1 deletions

View file

@ -2793,7 +2793,10 @@ static void zdiffAlgorithm2(zsetopsrc *src, long setnum, zset *dstzset, size_t *
/* Exit if result set is empty as any additional removal
* of elements will have no effect. */
if (cardinality == 0) break;
if (cardinality == 0) {
zuiDiscardDirtyValue(&zval);
break;
}
}
zuiClearIterator(&src[j]);

View file

@ -1114,6 +1114,18 @@ start_server {tags {"zset"}} {
assert_equal {a 1 e 5} [r zrange zsete{t} 0 -1 withscores]
}
test "ZDIFF algorithm 2 empty result early exit - $encoding" {
# Force algorithm 2 by inflating setnum with non-existing keys.
# algo_one_work = len(src[0]) * setnum / 2 = 2 * 10 / 2 = 10
# algo_two_work = 2 + 2 + 0*8 = 4
# algo_one (10) > algo_two (4) -> algorithm 2 is selected
r del zseta{t} zsetb{t} zsetc{t}
r zadd zseta{t} 1 a 2 b
r zadd zsetb{t} 1 a 2 b
assert_equal 0 [r zdiffstore zsetc{t} 10 zseta{t} zsetb{t} nx1{t} nx2{t} nx3{t} nx4{t} nx5{t} nx6{t} nx7{t} nx8{t}]
assert_equal {} [r zrange zsetc{t} 0 -1 withscores]
}
test "ZDIFF fuzzing - $encoding" {
for {set j 0} {$j < 100} {incr j} {
unset -nocomplain s