From eeb64c3acdf821b1df58f332493ea131221aa510 Mon Sep 17 00:00:00 2001 From: Warrick <1016weicheng@gmail.com> Date: Wed, 4 Feb 2026 17:49:38 +0800 Subject: [PATCH 1/4] [fix] memtest && updateClientMemUsageAndBucket --- src/debug.c | 27 +++++++++++++++++++++++++++ src/networking.c | 10 +++++++++- src/server.c | 9 +++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/debug.c b/src/debug.c index 88af0a22dc6..4a43d01de86 100644 --- a/src/debug.c +++ b/src/debug.c @@ -2288,6 +2288,14 @@ int memtest_test_linux_anonymous_maps(void) { end_addr = strtoul(end,NULL,16); size = end_addr-start_addr; + if (regions >= MEMTEST_MAX_REGIONS) { + snprintf(logbuf,sizeof(logbuf), + "*** Too many memory regions (max %d), skipping remaining regions\n", + MEMTEST_MAX_REGIONS); + if (write(fd,logbuf,strlen(logbuf)) == -1) { /* Nothing to do. */ } + break; + } + start_vect[regions] = start_addr; size_vect[regions] = size; snprintf(logbuf,sizeof(logbuf), @@ -2338,6 +2346,24 @@ void killThreads(void) { void doFastMemoryTest(void) { #if defined(HAVE_PROC_MAPS) +#if defined(__has_feature) +# if __has_feature(address_sanitizer) +# define RUNNING_ASAN 1 +# endif +#endif +#ifdef __SANITIZE_ADDRESS__ +# define RUNNING_ASAN 1 +#endif + +#ifdef RUNNING_ASAN + /* Skip memory test when running under AddressSanitizer as it will + * interfere with ASAN's shadow memory and cause false positives. */ + if (server.memcheck_enabled) { + serverLogRaw(LL_WARNING|LL_RAW, "\n------ FAST MEMORY TEST ------\n"); + serverLogRaw(LL_WARNING|LL_RAW, + "Memory test skipped: running under AddressSanitizer\n"); + } +#else if (server.memcheck_enabled) { /* Test memory */ serverLogRaw(LL_WARNING|LL_RAW, "\n------ FAST MEMORY TEST ------\n"); @@ -2350,6 +2376,7 @@ void doFastMemoryTest(void) { "Fast memory test PASSED, however your memory can still be broken. Please run a memory test for several hours if possible.\n"); } } +#endif #endif /* HAVE_PROC_MAPS */ } diff --git a/src/networking.c b/src/networking.c index 907687bf063..85f5d6faede 100644 --- a/src/networking.c +++ b/src/networking.c @@ -1810,17 +1810,25 @@ void freeClientsInDeferedQueue(void) { listIter li; listNode *ln; + /* We can safely use listIter here because listNext() saves the next node + * pointer BEFORE returning current node, so deleting current node doesn't + * invalidate the iterator. However, we must delete the node BEFORE calling + * freeClient() to avoid use-after-free if freeClient() recurses. */ listRewind(server.clients_to_free, &li); while ((ln = listNext(&li))) { client *c = listNodeValue(ln); if (!c->keyrequests_count) { client_desc = catClientInfoString(sdsempty(), c); c->CLIENT_DEFERED_CLOSING = 0; + /* IMPORTANT: Remove from list BEFORE freeing to avoid: + * 1. Use-after-free if freeClient() recurses back here + * 2. Iterator pointing to freed memory */ + listDelNode(server.clients_to_free, ln); freeClient(c); serverLog(LL_NOTICE, "Defered client closed: %s", client_desc); sdsfree(client_desc); - listDelNode(server.clients_to_free,ln); } + /* If client has pending key requests, skip it and continue with next client */ } } diff --git a/src/server.c b/src/server.c index 58795faf3eb..28ea1454235 100644 --- a/src/server.c +++ b/src/server.c @@ -1067,8 +1067,17 @@ int updateClientMemUsageAndBucket(client *c) { * running_tid is the main thread. The true main thread is allowed to call * this function on clients handled by IO-threads as it makes sure the * IO-threads are paused, f.e see cleintsCron() and evictClients(). */ +#ifdef ENABLE_SWAP + if (!((pthread_equal(pthread_self(), server.main_thread_id) || + c->running_tid == IOTHREAD_MAIN_THREAD_ID) && c->conn)) { + return 0; + } + +#else serverAssert((pthread_equal(pthread_self(), server.main_thread_id) || c->running_tid == IOTHREAD_MAIN_THREAD_ID) && c->conn); +#endif + int allow_eviction = clientEvictionAllowed(c); removeClientFromMemUsageBucket(c, allow_eviction); From a92c463fbcaf7fe4d4b3d4819a037d12bf8082fc Mon Sep 17 00:00:00 2001 From: Warrick <1016weicheng@gmail.com> Date: Thu, 5 Feb 2026 14:02:48 +0800 Subject: [PATCH 2/4] [test] failover --- src/Makefile | 2 +- tests/integration/failover.tcl | 26 ++++++++++++++++---------- tests/test_helper.tcl | 3 +++ 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/Makefile b/src/Makefile index 9d8125609b3..a91fd52da7e 100644 --- a/src/Makefile +++ b/src/Makefile @@ -570,7 +570,7 @@ test: $(REDIS_SERVER_NAME) $(REDIS_CHECK_AOF_NAME) $(REDIS_CLI_NAME) $(REDIS_BEN @(cd ..; ./runtest) test-asan: $(REDIS_SERVER_NAME) $(REDIS_CHECK_AOF_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) - @(cd ..; ./runtest --tags -nosanitizer) + @(cd ..; ./runtest --tags -nosanitizer --asan) test-modules: $(REDIS_SERVER_NAME) @(cd ..; ./runtest-moduleapi) diff --git a/tests/integration/failover.tcl b/tests/integration/failover.tcl index bd33f84aba6..4b623803040 100644 --- a/tests/integration/failover.tcl +++ b/tests/integration/failover.tcl @@ -1,4 +1,10 @@ start_server {tags {"failover external:skip"} overrides {save {}}} { + if {$::asan} { + # ASAN needs more time to complete the failover + set failover_timeout 200 + } else { + set failover_timeout 50 + } start_server {overrides {save {}}} { start_server {overrides {save {}}} { set node_0 [srv 0 client] @@ -83,8 +89,8 @@ start_server {overrides {save {}}} { # Execute the failover $node_0 failover to $node_1_host $node_1_port - # Wait for failover to end - wait_for_condition 50 100 { + # Wait for failover to end (increased timeout for ASAN compatibility) + wait_for_condition $failover_timeout 100 { [s 0 master_failover_state] == "no-failover" } else { fail "Failover from node 0 to node 1 did not finish" @@ -118,8 +124,8 @@ start_server {overrides {save {}}} { $node_1 set CASE 1 $node_1 FAILOVER - # Wait for failover to end - wait_for_condition 50 100 { + # Wait for failover to end (increased timeout for ASAN compatibility) + wait_for_condition $failover_timeout 100 { [s -1 master_failover_state] == "no-failover" } else { fail "Failover from node 1 to node 2 did not finish" @@ -149,8 +155,8 @@ start_server {overrides {save {}}} { $node_2 set case 2 $node_2 failover to $node_0_host $node_0_port TIMEOUT 100 FORCE - # Wait for node 0 to give up on sync attempt and start failover - wait_for_condition 50 100 { + # Wait for node 0 to give up on sync attempt and start failover (increased timeout for ASAN) + wait_for_condition $failover_timeout 100 { [s -2 master_failover_state] == "failover-in-progress" } else { fail "Failover from node 2 to node 0 did not timeout" @@ -163,8 +169,8 @@ start_server {overrides {save {}}} { resume_process $node_0_pid - # Wait for failover to end - wait_for_condition 50 100 { + # Wait for failover to end (increased timeout for ASAN compatibility) + wait_for_condition $failover_timeout 100 { [s -2 master_failover_state] == "no-failover" } else { fail "Failover from node 2 to node 0 did not finish" @@ -267,8 +273,8 @@ start_server {overrides {save {}}} { $node_0 failover to $node_1_host $node_1_port resume_process [srv -1 pid] - # Wait for failover to end - wait_for_condition 50 100 { + # Wait for failover to end (increased timeout for ASAN compatibility) + wait_for_condition $failover_timeout 100 { [s 0 master_failover_state] == "no-failover" } else { fail "Failover from node_0 to replica did not finish" diff --git a/tests/test_helper.tcl b/tests/test_helper.tcl index 883eab0f679..2e241a8bca8 100644 --- a/tests/test_helper.tcl +++ b/tests/test_helper.tcl @@ -78,6 +78,7 @@ set ::portcount 8000; # we don't wanna use more than 10000 to avoid collision wi set ::traceleaks 0 set ::valgrind 0 set ::tsan 0 +set ::asan 0 set ::durable 0 set ::tls 0 set ::tls_module 0 @@ -650,6 +651,8 @@ for {set j 0} {$j < [llength $argv]} {incr j} { set ::valgrind 1 } elseif {$opt eq {--tsan}} { set ::tsan 1 + } elseif {$opt eq {--asan}} { + set ::asan 1 } elseif {$opt eq {--stack-logging}} { if {[string match {*Darwin*} [exec uname -a]]} { set ::stack_logging 1 From a1be7356d99751bb403a68a995eecf010849a0b0 Mon Sep 17 00:00:00 2001 From: Warrick <1016weicheng@gmail.com> Date: Thu, 5 Feb 2026 14:19:29 +0800 Subject: [PATCH 3/4] [debug] rm gtid --- tests/test_helper.tcl | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_helper.tcl b/tests/test_helper.tcl index 2e241a8bca8..e97c3555377 100644 --- a/tests/test_helper.tcl +++ b/tests/test_helper.tcl @@ -52,7 +52,6 @@ proc load_tests {dir test_dirs} { } set ::gtid_test_dirs { - gtid } set ::gtid_tests [load_tests $dir $::gtid_test_dirs] From 1d56d0792dfc6dc5df187fae535239a4acdd425a Mon Sep 17 00:00:00 2001 From: Warrick <1016weicheng@gmail.com> Date: Thu, 5 Feb 2026 16:59:33 +0800 Subject: [PATCH 4/4] [fix] deferFreeClient --- src/networking.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/networking.c b/src/networking.c index 85f5d6faede..b7acad84bf2 100644 --- a/src/networking.c +++ b/src/networking.c @@ -1793,6 +1793,7 @@ static void resetReusableQueryBuf(client *c) { static void deferFreeClient(client *c) { sds client_desc; serverAssert(c->keyrequests_count); + if (c->CLIENT_DEFERED_CLOSING) return; client_desc = catClientInfoString(sdsempty(), c); serverLog(LL_NOTICE, "Defer client close: %s", client_desc);