Repository navigation
Conversation
|
Hi @importcpp, 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. |
importcpp
marked this pull request as ready for review
November 14, 2026 10:04
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #3787
Problem
Promotion and changing an existing upstream synchronously stop and join the replication thread. Today an idle event loop observes the stop flag only at its next 100 ms timer tick. That timer-phase wait unnecessarily extends the admin command and its exclusive command-guard hold time.
Change
StopMode::kWakeEventLoop, used only byServer::AddMasterandServer::RemoveMasterwhen they stop an existing replication thread.event_active; keepTimerCBresponsible for cleanup on the replication thread.TimerCBinterval, cleanup time, and total Stop time in microseconds. The cleanup interval includes the preceding stop-observation log, not only resource release.Stop()on the timer path, includingServer::Stop, which can be called by the existing signal handler.No timer interval change, new polling loop, replication catch-up shortcut, early command-lock release, Controller change, or metadata-persistence optimization is included. Active wakeup removes an idle-loop wait; it cannot preempt a running callback, a blocking connect, full sync, or a RocksDB write. This patch does not redesign signal shutdown or command-lock ordering.
Regression Coverage
Four C++ tests exercise production Stop/TimerCB/publication logic with a controlled event loop: idle wakeup, stop before publication, no-argument Stop retaining the timer path, and a timer-expiry/wakeup race repeated 100 times. They check thread join and event/base withdrawal. The idle test uses a five-second fallback timer and a one-second latency bound; the early-publication case also checks that the event is active before dispatch starts. Test runners impose outer timeouts because a broken join cannot be bounded by an assertion in the same thread.
The Go integration test performs 10 promotion rounds with three nodes. Each round writes 128 unique values, waits for the target offset, promotes the target, reverses the old primary, changes a third replica's existing upstream, and verifies all accumulated values on all nodes. It waits for the old feed connection to be reaped before reattaching the old primary.
Reproduction and Results
Ubuntu 24.04.4 LTS / Linux 6.8.0-100-generic aarch64 VM, 8 vCPUs, 12 GiB RAM, Clang 18.1.3, Release, jemalloc disabled, redis-cli 7.0.15. Thirty promotions per build, using the same script in sequential runs with no background workload or Controller:
885ace6d0477cf41bc2a5c34df79940cdb9c7049Both runs completed all 30 promotions with the expected role and marker value.
See the linked issue for the standalone two-node Python/redis-cli reproducer. The measurements include client process startup and connection setup; they are not a guaranteed failover bound or a GET/SET error-rate improvement claim.
Validation
The first ASan integration run failed the new test's five-second wait for the old primary's feed connection to be reaped. The promotion had already completed, with Stop logged at 364 us, and there was no sanitizer report. After allowing ten seconds for that asynchronous, ping-driven connection cleanup, all seven selected integration tests passed. This changes a test wait only, not a production timeout. The initial failure is not counted as a pass.
Formatting (
x.py check format, clang-format 18) andx.py check golangci-lintpassed. Changed-line clang-tidy 21 passed, as did full-file tidy of the new C++ test. Full-file tidy of the production translation units still reports three existing diagnostics in untouched code:mask/omaskinitialization and vector capacity infetchFiles; these are not changed by this patch.Linux sanitizer builds use
-DDISABLE_JEMALLOC=ON,-DCMAKE_BUILD_TYPE=RelWithDebInfo, and-O1 -gline-tables-only -DNDEBUGfor both C and C++, with either-DENABLE_ASAN=ONor-DENABLE_TSAN=ON. The second TSan configuration additionally uses-DENABLE_LUAJIT=OFF; Release and ASan retain LuaJIT. Bothkvrocksandunittestare built. The Go test executable was cross-compiled with Go 1.26.3 (GOOS=linux GOARCH=arm64 CGO_ENABLED=0) and executed inside the Linux VM; the server binaries are native Linux builds.TSan/LuaJIT limitation: On this ARM64 VM, the full focused selection with LuaJIT enabled crashed in
lua_pushcclosure -> lua::LoadLibraries -> lua::CreateStatewhile constructingServerWaitTest.EmptyMap, after the four new tests. The original 18 Cluster/ServerWait tests, without the new suite, passed 20 repetitions on both the unmodified base and patched binaries. A separate LuaJIT+TSan-only process returned NULL fromluaL_newstate()in 4/50 fresh processes; the same source without TSan returned no NULLs in 50 runs. This reproduces an initialization failure without Kvrocks, Libevent, or this patch, but does not establish the exact cause of the full-suite crash or rule out test-order effects. The LuaJIT-enabled TSan matrix remains failed; the LuaJIT-disabled result is separate, not a fix for the original crash. No Lua changes or new suppression rules are included.Independent LuaJIT initialization probe
LuaJIT revision:
1968b356c2ecbe729e3998e063b9813732644966, as pinned by this Kvrocks base. The probe links the same static library used by the initial LuaJIT-enabled TSan build.Using the dependency directories from a LuaJIT-enabled TSan build:
Observed: NULL at iterations 3, 9, 42, and 45 (exit 2). Zero user-created threads in this run. Removing
-fsanitize=threadand repeating produced 0/50 NULL results. These are local observations, not a portable failure-rate estimate.Sanitizer runs use the repository's existing
tests/lsan-suppressionsandtests/tsan-suppressionswithout adding suppression rules. TSan's existing signal-handler suppression means these runs do not validate signal safety. They also do not prove that all possible thread interleavings are race-free.Reproduce the focused test selection after building each configuration (set
BUILDto that build directory):Review Focus and AI Assistance
OpenAI Codex assisted with diagnosis, implementation, regression tests, local validation, and drafting this PR. A separate Codex chat reviewed the code and this description; this is not a human maintainer review. Please pay particular attention to event lifetime, stop-before-publication, lock ordering around join, and preserving the default shutdown path.
The local focused matrix is not a substitute for the upstream full CI matrix and maintainer review.