From 2ba0194fbe5820cab8602bfa633a7d27e97cabdd Mon Sep 17 00:00:00 2001 From: Guimu <30684111+daguimu@users.noreply.github.com> Date: Fri, 27 Mar 2026 22:09:57 +0800 Subject: [PATCH] Fix memory leak in ZDIFF algorithm 2 on early exit (#14932) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 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. --- src/t_zset.c | 5 ++++- tests/unit/type/zset.tcl | 12 ++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/t_zset.c b/src/t_zset.c index 3ce602e1d..346bcd38c 100644 --- a/src/t_zset.c +++ b/src/t_zset.c @@ -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]); diff --git a/tests/unit/type/zset.tcl b/tests/unit/type/zset.tcl index 9f3e1f334..ad9483b2d 100644 --- a/tests/unit/type/zset.tcl +++ b/tests/unit/type/zset.tcl @@ -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