Support CLIENT NO-TOUCH as a connection-setup command - #526
Support CLIENT NO-TOUCH as a connection-setup command#526fcostaoliveira wants to merge 40 commits into
Conversation
…command Fixes #525. NO-TOUCH is connection-scoped state that must be set once before the measured workload starts, not re-sent every N ops like --command would; this mirrors how AUTH/SELECT/HELLO already get setup-time treatment. Redis protocol only (7.0+); errors loudly on unsupported servers instead of silently benchmarking the wrong thing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…Redis <7.0 Follow-up to 69c5ac2, from adversarial review: the man page and bash-completion list were missing the new flag (existing PRs #468/#381/#379 update these directly rather than only at release time), and the functional test needed a version guard since CLIENT NO-TOUCH is Redis 7.0+ only. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
From adversarial review: unlike READONLY (replica-only), CLIENT NO-TOUCH arms on every connection including primaries, so a primary shard discovered mid-run via a live CLUSTER SLOTS refresh (attached through bufferevent_socket_new + a later bufferevent_socket_connect, same as replica discovery) has no later setup command to incidentally force the EPOLLOUT flush that READONLY gets for free from its sibling send. Route CLIENT NO-TOUCH through the same bufferevent_write path as READONLY instead of the plain evbuffer_add path, for the same reason. Also re-check is_redis_protocol at arm time in connect() (defense in depth alongside the existing CLI-time gate), matching the READONLY guard immediately above it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
🤖 Automated first-pass review — a human maintainer's review is still required before merge. Thanks for this — went through the ladder threading and the CLI/doc surface, a few comments below. The Two things I'd want settled first: RLTest dependency ordering. The README now says "This repo's pinned RLTest fork ( The second literal RESP string. AUTH/SELECT/HELLO/CLUSTER SLOTS all go through Smaller things, none blocking:
The core change looks solid and unusually well covered by tests; I'd mainly want #531 sequenced ahead of this and the (would file this as a comment, not a formal block) |
…ordering gap - clang-format (CI's format-check job was failing on shard_connection.cpp). - Drop the unused write_command_client_no_touch() virtual and its two assert(0) memcache stubs, per review: nothing called it (the wire bytes are sent inline via bufferevent_write in send_conn_setup_commands, same as READONLY), so it was dead code duplicating the wire format in a second place with no compile-time link between the two. - Re-arm and send the connection-setup ladder before drain_replay_queue_after_reconnect() on reconnect, not after. Previously replayed in-flight requests could land on a fresh connection before AUTH/SELECT/HELLO/CLIENT-NO-TOUCH/READONLY did, since fill_pipeline() (which fires the ladder) ran after the replay drain. For AUTH that surfaces as a loud NOAUTH; for CLIENT NO-TOUCH it silently touched the exact recency the flag exists to protect. No-op when no ladder step is configured (the common case). - Re-wrap the --client-no-touch usage() entry across explicit lines at the existing 33-column continuation indent, matching neighboring multi-line entries. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Per Cursor Bugbot, verified against this repo's own
deps/commands_json/client-no-touch.json ("since": "7.2.0"): the command
shipped in Redis 7.2, not 7.0. Corrects --help text, the man page, and
the functional test's version-skip guard (was silently letting 7.0/7.1
run and fail instead of skipping). My own mistake, carried over from how
I originally scoped issue #525.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… snapshot once coverage-ubuntu failed test_without_client_no_touch_resets_idle_time with before=2 after=2 despite memtier reporting 58805 real GET hits (0 misses) against the key during the run -- the traffic genuinely landed. Root cause: server.lruclock (what OBJECT IDLETIME reads) is a 1-second- quantized value refreshed by serverCron, not a live timestamp (see LRU_CLOCK() in evict.c). Checking it exactly once immediately after the benchmark run assumes a cron tick already landed; on a loaded/throttled CI host it can lag. The positive-control test in the same file/job passed correctly moments earlier, and this reproduces reliably locally without this fix, so this is a CI-only timing flake in the test's assertion timing, not the feature itself. Fix: poll OBJECT IDLETIME for up to 3s instead of checking once. A real regression (GETs not updating recency when they should) still fails once the timeout is exhausted -- this only tolerates the clock's own known quantization lag, it doesn't weaken what the test proves. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CI (Test Single Endpoint: TCP Plaintext) failed with "idle time should have accrued before the run; got 1" -- the same OBJECT IDLETIME quantization/lag issue as the previous fix, just on the "before" snapshot instead of "after": a fixed sleep(2) followed by one immediate check has zero margin against server.lruclock's 1-second-quantized, cron-driven updates lagging on a loaded CI host. Generalize the polling helper to take a predicate and use it for both the "accrues to >= 2" and "drops below" checks in both tests, replacing every fixed-sleep-then-snapshot pattern. Verified end-to-end against a real redis-server for both the positive and negative-control flows. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Per review: the setup-ladder-before-replay-drain reordering affects every --authenticate/--select-db/HELLO/READONLY user on reconnect, not just --client-no-touch, and deserves its own PR with dedicated regression-test work rather than riding along here. While building that test I found the race is genuinely hard to pin down reliably (a CLIENT-KILL-churn stress test only catches it probabilistically, and aggressive kill intervals cause stalls under both the buggy and fixed code, so "does it hang" isn't a discriminating signal either) -- filed as #527 with what I tried. This restores the original pre-377f819 ordering and the honest "known limitation" disclosure in the PR description. --client-no-touch's own arming (in connect()) is unaffected by this revert. Also, per review nitpick: restore maxmemory-policy to its prior value after tests/test_client_no_touch.py runs instead of leaving the override on the shared master. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Thanks for the thorough re-review — went through each point:
PR description updated to match (test-plan checkbox corrections, known-limitation note restored, #527 linked). |
…, add comments - tests/test_client_no_touch.py: the positive test only asserted idle_after >= idle_before, which passes vacuously if zero GETs actually reached the key. Assert ALL STATS.Gets.Count > 0 from mb.json first, so the test proves traffic happened under --client-no-touch specifically, not just that idle time didn't decrease (which zero traffic also satisfies). - --help / man page: document the #527 gap under --client-no-touch's own entry -- --retry-on-error can currently let a replayed request reach the server before NO-TOUCH re-arms on a fresh connection. - shard_connection.h: explain why is_ready_for_reads() intentionally excludes m_no_touch_state (contrasted with is_conn_setup_done(), which includes it) rather than leaving it as a silent asymmetry. - shard_connection.cpp: fix a misleading comment -- NO-TOUCH's position after HELLO is about wire ordering in one pipelined write, not about HELLO's RESP3 negotiation having completed (its reply is a plain +OK either way). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Appreciate the ones you went and checked yourself too (
All pushed. PR description's test-plan and follow-ups sections updated to match. |
…jection test - Warn at startup (not just document) when --client-no-touch is combined with --retry-on-error, referencing issue #527. The person most likely to be bitten by the silent gap is exactly the one who never reads the man page. - Trim --help back down: it had become the longest entry in the block and the only one referencing an issue number; the #527 detail now lives only in the man page, matching this project's existing convention of not citing issue numbers in --help text. - tests/test_client_no_touch.py: new test_client_no_touch_rejected_by_server_fails_loudly, using an ACL user with 'client|no-touch' denied to reproduce the exact -NOPERM rejection an old pre-7.2 server's -ERR would produce, on a real 7.2+ server -- proves the "unsupported/erroring server fails loudly" claim in CI without needing an actual old Redis binary. - New tests/test_client_no_touch_cluster.py: verifies CLIENT NO-TOUCH lands on a replica shard connection specifically, since every replica is always created via the same bufferevent_socket_new + later bufferevent_socket_connect path the bufferevent_write choice exists for (only the single bootstrap seed connection uses the direct-connect path). Skips gracefully under RLTest's --use-slaves, which produces slaves that are not cluster-gossip members and therefore invisible to get_cluster_replica_connections() -- a known, already-tracked harness gap (README "Testing limitations", issue #462) that also blocks this project's existing --read-preference test suite the same way. Verified manually against a real CLUSTER MEET/REPLICATE cluster: CLIENT NO-TOUCH ON is the first command MONITOR observes on the replica connection. - Condensed the two comments the previous round called out as too long / misplaced (NO-TOUCH send rationale now points at READONLY's comment instead of restating it; is_ready_for_reads() note moved to sit with the function instead of trailing after its closing brace). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Went through all four:
Cluster-path test: this ran into a real, pre-existing wall —
Comment nitpicks: both shortened as suggested — the NO-TOUCH send comment now points at READONLY's comment instead of restating it, and the PR description and test-plan updated. |
- config_print() (--show-config output) was missing client-no-touch, even though config_print_to_json() (mb.json) already had it -- added next to select-db/no-expiry, matching the JSON echo's placement. - cluster_client.cpp:1946: the comment spelling out is_conn_setup_done()'s predicate for the cross-shard read-routing readiness explanation was one member short (missing m_no_touch_state). Real drift risk since that comment does real explanatory work for a routing decision. - tests/test_cli_validation_client_no_touch.py: docstring said "Tests covered: 1. / 2." but the file has three test functions (both memcache variants were folded into one bullet). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
All three real ones fixed, plus the nitpick:
Also confirms the ASAN OSS-CLUSTER TLS reconnections failure on this run was an unrelated pre-existing flake ( |
…imary CI test - shard_connection.h: the is_ready_for_reads() comment I added two rounds ago overclaimed. For a primary connection, m_cluster_slots stays setup_done trivially (only MAIN_CONNECTION ever arms it) and m_role != role_replica, so the predicate is satisfied the instant conn_connected is reached -- before send_conn_setup_commands() has written NO-TOUCH at all. The exclusion is still correct, but for the *other* reason already given (fill_pipeline gates actual sends on is_conn_setup_done(), which does include m_no_touch_state), not because NO-TOUCH's bytes are already ahead of it on the wire -- that part is only actually true for the replica/READONLY branch. Also noted the weaker claim for peer_client_has_any_setup_in_progress(), which asks a different question than "can I route a read here." - shard_connection.cpp: fixed the READONLY comment's "Primaries don't hit this because all setup states default to setup_done, so send_conn_setup_commands is a no-op for them" -- now flatly wrong with --client-no-touch, which does arm and send for primaries. Scoped the claim to the specific READONLY branch it's actually about. - tests/test_client_no_touch_cluster.py: added a second test targeting a non-seed PRIMARY shard connection instead of a replica. cluster_client's CLUSTER-SLOTS-reply loop connects every shard beyond memtier's single bootstrap seed connection via connect_shard_connection() -- the same "discovered" path a replica uses -- so this needs no replicas and no --use-slaves, and actually runs (not skips) in the standard OSS-CLUSTER CI matrix. Verified against a real 2-shard cluster: CLIENT NO-TOUCH ON is the first command MONITOR observes on the non-seed shard's connection. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Good catch on the comment, and good push on the test:
READONLY comment: fixed, scoped to the branch it's actually about now. "Is there anything cheaper": yes, and thanks for pushing on it — All pushed, PR description updated. |
Test OSS-CLUSTER API: TCP TLS failed with "monitor observed: []" -- test_client_no_touch_lands_on_non_seed_primary_discovered_via_cluster_slots connected to the non-seed shard via a plain redis.Redis(host, port), which silently fails its TLS handshake against a TLS-only cluster matrix cell. _capture_monitor's broad except swallowed the connection error, so the MONITOR thread just never captured anything -- not an obvious TLS error in the test output. Fixed by mirroring tests/test_mget_protocol.py's _get_redis_conn() TLS handling (env.useTLS -> ssl=True, ssl_cert_reqs="none", client cert/key). Verified against a real TLS-enabled 2-shard cluster: CLIENT NO-TOUCH ON is now correctly captured as the first command on the non-seed shard. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…t errors - shard_connection.h: collapsed the is_ready_for_reads() m_no_touch_state comment from ~22 lines to one, matching how m_authentication/ m_db_selection/m_hello (excluded for the identical reason) get no comment at all. The dropped hedging about peer_client_has_any_setup_in_progress() wasn't wrong, just review-thread residue that didn't belong in the source once the core reasoning collapsed to "same as the others." - shard_connection.cpp: get_last_request_type() returned "CLIENT_NO-TOUCH" (underscore) where "HELLO"/"READONLY" have no such separator -- surfaces in the crash-handler dump's last_cmd=. Now "CLIENT NO-TOUCH". - tests/include.py: lifted the TLS-aware connection helper and MONITOR- capture thread target out of test_client_no_touch_cluster.py into get_redis_conn_for_node()/capture_monitor(), the latter now taking an optional `errors` list instead of a bare `except: pass` -- exactly the swallowed exception that turned last round's TLS-handshake failure into an unreadable "monitor observed: []" instead of an actionable error. tests/test_client_no_touch_cluster.py's two tests now share one _run_and_check_no_touch() helper built on top of these, cutting the file from two near-duplicate ~85-line tests to two ~15-line ones. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Good catch on all three, and fair pushback on my own hedging: Comment collapse: agreed, and slightly embarrassed I didn't notice the inconsistency with the three siblings myself. Collapsed to one line. Swallowed exception: yes — lifted the connection/monitor helpers into
Hard error vs. warning for All pushed. |
…al DRY fold-over - .github/workflows/ci.yml: every "OSS-CLUSTER + replicas" matrix cell pins TEST: to a specific test_read_preference_*.py file, so test_client_no_touch_cluster.py was never loaded under --use-slaves at all -- not just blocked by #462 as I'd assumed. Added its own matrix cell ("OSS-CLUSTER + replicas: client-no-touch") so the replica test actually gets a chance to run once #462 lands, and the non-seed-primary test gets exercised under --use-slaves too in the meantime. - memtier_benchmark.cpp: memtier_benchmark.1 is "DO NOT MODIFY -- generated by help2man" and AGENTS.md treats it as regenerated at release time, but the seventh round trimmed the #527 caveat out of usage() -- meaning the next regen would silently drop the only place that gap is documented. Added a short line back (no issue number, unlike the fuller man-page version, so it doesn't reprise the earlier "longest entry" objection). - tests/include.py + tests/test_mget_protocol.py: get_redis_conn_for_node()'s docstring claimed the TLS-connection boilerplate "used to be duplicated" in test_mget_protocol.py, but that file still had its own copy. Actually folded test_mget_protocol.py's _get_redis_conn()/_capture_monitor() over to the shared helpers (capture_monitor imported as _capture_monitor to keep _start_monitor's existing call site unchanged) instead of just fixing the docstring -- real deduplication, not a claim of one. Verified all 7 of that file's tests still pass. - tests/test_client_no_touch_cluster.py: dropped the explicit --cluster-mode arg; add_required_env_arguments() already appends it once env.isCluster() is true. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Thanks for actually reading the CI yaml rather than trusting my sixth-round claim — you were right and I wasn't: CI matrix gap: confirmed, every Man-page regen: good catch, and I should have thought of this myself given round 4 was literally me trimming that same note out of
All pushed. |
… in CI The new "OSS-CLUSTER + replicas: client-no-touch" matrix cell just proved the assumption wrong: test_client_no_touch_lands_on_replica_discovered_via_cluster_slots genuinely ran (not skipped) and passed, with READONLY immediately following CLIENT NO-TOUCH ON in the captured MONITOR output -- direct proof it hit a real, cluster-gossip-visible replica connection. README's "Testing limitations" section and issue #462 describe RLTest's --use-slaves as producing gossip-invisible slaves, but this repo's own test_requirements.txt already pins a fork that fixes exactly that (issues real CLUSTER MEET + CLUSTER REPLICATE). That fix predates this PR; I'd just inherited the stale assumption from README/#462 without checking whether it still applied to this repo's actual current test dependencies. Corrected the module docstring and skip-path comment to describe verified reality instead: the test runs for real, and the env.skip() fallback is defensive housekeeping, not the expected/normal path. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…er, sync man page text - tests/include.py: get_cluster_replica_connections() still built a plain redis.Redis(host, port) with no TLS kwargs -- the same class of bug as the non-seed-primary test hit two rounds ago, just in a call site round 7's consolidation missed. Doesn't bite in the current CI matrix (the replica cell is TLS off, the TLS cluster cell has no --use-slaves), but is a real landmine for the next matrix cell that combines both. Routed it through get_redis_conn_for_node(), which now accepts **extra_kwargs to preserve this call site's decode_responses=True and socket_connect_timeout=5. Verified against a real cluster+replica. - memtier_benchmark.1: the hand-written man page entry still carried round-3's longer "Known gap ... issue #527" wording while usage() carries round-8's shorter "Caveat under --retry-on-error: see startup warning" -- exactly the drift round 8 was trying to prevent, just not actually finished. Made them the same string. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Both real ones fixed:
Man page /
Warning-vs-hard-error human sign-off: agreed, that's the one call in this PR that deserves an explicit human decision rather than my own reasoning (however sound) plus automated-review approval. Flagging in the PR description for whoever does final review. All pushed. |
- tests/include.py: get_redis_conn_for_node()'s own docstring already
claimed it accepted a node's 'host' key, but the implementation
hardcoded 127.0.0.1 -- last round's fix to get_cluster_replica_connections()
therefore left `host` a dead local and silently stopped dialing the
gossiped host for cluster_replica_connections()'s five existing
read-preference callers. Verified RLTest's own getMasterNodesList()
always sets host (to 'localhost', both standalone and cluster envs), so
`node.get("host") or "127.0.0.1"` is safe for every caller: falls back
identically to before for callers that don't pass a host (test_mget_protocol.py,
the non-seed-primary test), and get_cluster_replica_connections() now
passes the real gossiped host through instead of dropping it. Verified
against a real cluster+replica.
- tests/test_client_no_touch_cluster.py: trimmed the module docstring,
which had grown to narrate round-9's discovery process rather than just
stating current, verified facts.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…o README, note DRY gap - test_client_no_touch.py: new test_client_no_touch_resent_after_reconnect -- kills the live memtier connection mid-run via CLIENT KILL and confirms CLIENT NO-TOUCH ON appears on MONITOR a second time after it reconnects. Previously this claim (m_no_touch_state re-armed on every reconnect) was only exercised by a manual reconnect stress run in the PR's test plan, not a committed test. - Moved the #527 pointer out of --help/the man page (reverting round 20's addition) and into a new README "Known limitations" section instead: an issue number with no context isn't that useful to someone reading --help output, and it would go stale silently once #527 lands. Also made --client-no-touch's --help entry the same length as its siblings again. - get_redis_conn_for_node(): documented that, unlike test_mget_protocol.py's near-identical _get_redis_conn(), it has no unix-socket branch -- fine for its only current caller (cluster-only), but noted so it isn't a silent trap for whoever reaches for it next. Left the two helpers otherwise separate; the round-12 scope-discipline reasoning for not folding test_mget_protocol.py onto them in this PR still applies. The RLTest-fork-pin-scope question (should the new cluster test's hard-fail land here or wait for the #462 follow-up) is answered in the PR thread, not with a code change: the hard-fail is scoped to this PR's own new test file, not any pre-existing test, and leaving it skip-tolerant would reproduce the exact silent-coverage-gap problem the dedicated CI cell was built to catch. Verified locally: test_client_no_touch.py 4/4, including the new test (observed the CLIENT KILL, the reconnect, and CLIENT NO-TOUCH ON twice on the MONITOR feed). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
All four, in order.
Rebuilt clean, format-check clean, |
… a comment, file #529 - rt_no_touch error message: append "(this command requires Redis 7.2+)" after the server's own -ERR text, as a reminder rather than a diagnosis (the ACL-denial test in this same PR fails this same message on a fully current server, so it can't claim that's *why* any given failure happened). Manually verified end-to-end under the real default --connection-stage-timeout (30s, not the test's accelerated 3s): the ACL- denial run aborts at ~30.0s wall-clock with exit code 2 and "aborting after 30 seconds of connection-stage failures (last error: CLIENT NO-TOUCH failed: ... (this command requires Redis 7.2+))." No hang, same behavior as the accelerated path just proven at 3s -- not worth adding as a second, 30s-slower committed test for that reason. - peer_client_has_any_setup_in_progress(): "Deliberately gates on is_ready_for_reads()" overstated this as settled design when the revert commit's own reasoning was "needs real investigation, not confirmed safe". Reworded, and filed #529 to track the actual investigation (whether the wider gate's livelock risk was real) so it doesn't get lost the way #527 almost did. - PR description: fixed the stale "--help/the man page carry a pointer to #527" line (round 21 moved that to README) and added an explicit "Open questions for a maintainer" section for the two things round 22's review flagged as decisions rather than bugs: where the #527 disclosure should live, and whether this PR should be the one to flip the cluster test from skip-tolerant to hard-fail on the RLTest fork. Both have reasoning already in the thread; surfacing them instead of resolving them over another review round. Verified locally: test_client_no_touch.py 4/4 (message-format change doesn't break the "CLIENT NO-TOUCH failed" substring the ACL test asserts on), rebuilt clean, format-check clean. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Appreciate the pattern-recognition on item 1 and 4 — you're right that those have been going in circles across rounds without new information each time, which is exactly the signal to stop self-adjudicating. Addressing in order.
Rebuilt clean, format-check clean, |
…e connection helper, fix the kill test, front-load the version hint - peer_client_has_any_setup_in_progress(): dropped the #529 pointer and "safer of two options tried" framing -- that's this PR's review history, not something the function's next reader needs. #529 stays the place to track the actual open question. - get_redis_conn_for_node() (tests/include.py): added the env.isUnixSocket() branch it was missing, and test_mget_protocol.py's _get_redis_conn() now delegates to it instead of carrying its own copy of the same TLS-kwargs logic (removed the now-unused `redis` import and TLS_CACERT/CERT/KEY imports it no longer needs directly). - test_client_no_touch_resent_after_reconnect: replaced the CLIENT LIST cmd/flags heuristic (which could kill any idle connection left over from an earlier test in this file, e.g. the maxmemory-policy config connection) with a pre-run client-id snapshot -- only ids that appear *after* the snapshot (i.e. memtier's) get killed now. - rt_no_touch error message: moved "(this command requires Redis 7.2+)" from trailing to leading. r->get_status() is server-controlled and unbounded against a fixed 256-byte buffer; trailing text after it can get silently truncated, and the version note is the whole reason the string exists, so it can't be the part that's optional. - README: moved "Known limitations" (the #527 note) down next to the other cluster/testing caveats instead of being the first thing under "Using memtier_benchmark", so a pre-existing, not-introduced-here gap isn't the first thing a new reader meets. - PR description: dropped the now-resolved "where to disclose #527" open question (round 23 gave a clear recommendation, applied above); kept the RLTest-fork-dependency question, which is still genuinely a maintainer call. Verified locally: test_client_no_touch.py 4/4 (the new snapshot-based kill logic exercised the same as before), test_mget_protocol.py 7/7 (the delegated connection helper works identically to its removed copy). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Good round, one real correctness bug in there (item 4). Taking them in order.
On your answer to the #527 disclosure question: took it — moved "Known limitations" down next to the other cluster/testing caveats instead of being the first thing under "Using memtier_benchmark", and dropped that item from the PR description's open-questions section since there's now a clear recommendation applied rather than an unresolved oscillation. Left the RLTest-fork-dependency question in place, since you were explicit that one isn't yours to decide either. Rebuilt clean, format-check clean. |
… the error-message cause - send_conn_setup_commands(): moved the CLIENT NO-TOUCH block from between HELLO and READONLY to after READONLY, before CLUSTER SLOTS. AUTH/SELECT/HELLO/READONLY now keep the exact relative wire order they've always had; nothing in the ladder depended on NO-TOUCH preceding READONLY specifically (it's one pipelined pass, no reply waited on in between), so this is a pure reorder, not a behavior change. Re-verified against real replica connections (dedicated OSS_CLUSTER_REPLICAS=1 cell, 2/2) since this changes wire order for those specifically (primaries were already unaffected -- READONLY is a no-op branch for them either way). - rt_no_touch error message: "(this command requires Redis 7.2+)" claimed a specific cause it can't actually know -- the ACL-denial test in this same PR hits this exact message on a fully current server. Reworded to "(may require Redis 7.2+ or be denied by ACL)", still leading (not trailing, per round 23) to avoid truncation. - PR description: updated to match the reordered ladder, and dropped the "Open questions for a maintainer" section -- round 24 gave a clear verdict on the one item still in it (keep the hard-fail; the SHA pin is the right mitigation for the actual risk, and it's already scoped to only the one cell that opts into it). - #528 (generic connection-setup-command flag): noted CLIENT NO-EVICT as another example raised this round, alongside NO-TOUCH/SETINFO/SETNAME/ REPLY already there. Verified locally: test_client_no_touch.py 4/4, dedicated replica cell 2/2 (both tests, including MONITOR observing NO-TOUCH on the reordered ladder). Rebuilt clean, format-check clean. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Thanks for closing out the RLTest question — taking your verdict (keep the hard-fail, the SHA pin covers the real risk, it's already scoped to the one cell that opts in) and dropped the now-resolved "Open questions" section from the description.
Rebuilt clean, format-check clean, re-verified |
…g, DRY the other monitor helper - test_client_no_touch_lands_on_replica_discovered_via_cluster_slots's hard-fail message now leads with "test-harness problem, not a memtier bug" and says explicitly that the test never got as far as sending CLIENT NO-TOUCH -- so whoever hits this in CI (if the RLTest fork ever stops behaving) doesn't have to reason that out from "expected at least one replica connection" on their own. Doesn't change the RLTest-fork- mirroring question itself (mirroring it under an org this project controls is a call about infrastructure this PR doesn't own). - test_mget_protocol.py's _capture_monitor()/_start_monitor(): same DRY as round 23's _get_redis_conn() -- _start_monitor() now targets include.py's capture_monitor() directly (single call site, compatible signature) instead of carrying its own near-identical copy. #462 confirmation (no code change): yes, that reading is right -- the `OSS-CLUSTER + replicas: read-preference` cell has been going green while skipping the replica path since the fork started producing visible replicas, which is exactly the narrowed scope #462's round-18 comment on this PR already describes. Verified locally: test_mget_protocol.py 7/7 (delegated capture_monitor behaves identically), dedicated replica cell 2/2. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Rebuilt clean (Python-only this round), re-verified |
…al RLTest-fork-availability issue - README "Known limitations": the ladder list still said "(AUTH, SELECT, HELLO, CLIENT NO-TOUCH, READONLY)" -- a leftover from before round 24 moved CLIENT NO-TOUCH to after READONLY in the actual code. Fixed to "(AUTH, SELECT, HELLO, READONLY, CLIENT NO-TOUCH)", matching send_conn_setup_commands()'s real order. - Filed #530 for the RLTest-fork-availability risk, now understood more precisely: every CI matrix cell across ci.yml/asan.yml/tsan.yml/ubsan.yml runs its own "pip install -r tests/test_requirements.txt" inside the per-cell job, so a fork that goes away breaks every Python-test cell at dependency-install time -- not just the one cluster cell that depends on the fork's cluster-aware --use-slaves *behavior*. This also means the skip-vs-hard-fail question asked across several review rounds is moot for this specific risk: pip install failing happens before any test collection, so it doesn't matter whether the downstream test would have skipped or hard-failed on missing replicas -- the job dies either way, earlier. Filed as its own issue rather than fixed here since mirroring the fork under an org-controlled location isn't something a feature PR can do. No test-code changes this round; the other two items (scope/split question, whether anything besides memtier plausibly connects during the reconnect test's kill window) are answered in the PR thread. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Rebuilt (README-only this round). |
…ith_slaves(), add show-config/JSON coverage - env_started_with_slaves(): the OSS_CLUSTER_REPLICAS env-var check (round 17) was wrong for a case this file's own module docstring documents -- running OSS_CLUSTER=1 OSS_CLUSTER_REPLICAS=1 together runs run_tests.sh's plain-cluster pass (no --use-slaves) and its replicas pass (with it) as two separate `python3 -m RLTest` invocations from one shell process, and OSS_CLUSTER_REPLICAS stays "1" in the environment for both. That made the replica test hard-fail with "test-harness problem, not a memtier bug" during the FIRST (plain) pass, where nothing was actually wrong -- a real false positive, just one no CI cell happens to trigger today. Switched to checking sys.argv for --use-slaves instead: run_tests.sh spawns a fresh RLTest subprocess per invocation, so it's correctly scoped per run, and it still doesn't depend on RLTest's internal env.envRunner attribute shape (the reason round 17 moved away from that in the first place). Reproduced the exact failing scenario locally before the fix and confirmed clean after: plain pass skips, replicas pass passes (MONITOR observing READONLY then CLIENT NO-TOUCH ON, per round 24's reorder). - test_cli_validation_client_no_touch.py: added test_client_no_touch_show_config, asserting --show-config prints "client-no-touch = yes"/"no" (config_print()'s yes/no convention, not config_print_to_json()'s true/false) and mb.json's configuration.client-no-touch matches "true" when the flag is set -- same shape as test_cli_validation_prometheus.py's V17 test. Nothing previously asserted on either output. - #528: noted CLIENT TRACKING as another example of the generic connection-setup-command question, alongside NO-EVICT/SETINFO/SETNAME/ REPLY already there. Verified locally: test_cli_validation_client_no_touch.py 4/4, and the exact OSS_CLUSTER=1 OSS_CLUSTER_REPLICAS=1 dual-invocation scenario from the module docstring end-to-end (skip in the plain pass, pass in the replicas pass). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Item 1 was a real bug, thanks for tracing it that precisely.
Verified locally: |
#531) tests/test_requirements.txt reverted to match master exactly -- the branch->SHA pin now lives in PR #531 instead, opened off master directly, so that CI-dependency change isn't gated on this feature's review (asked across rounds 23/26/28). Once #531 merges, this branch will pick up the SHA pin on the next rebase/merge from master with no separate action needed here. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…a-test gate, MONITOR-registration race fix, comment trims, README relocation - run_tests.sh: the OSS_CLUSTER_REPLICAS=1 block now sets MEMTIER_CLUSTER_REPLICAS_EXPECTED=1 inside its own subshell only (not globally). test_client_no_touch_cluster.py's replica test now hard- requires that dedicated, purpose-built signal instead of inferring "--use-slaves was passed" from env_started_with_slaves() -- the bot's point: a gate whose failure mode is "silently skip and the cell goes green having only exercised the non-seed-primary case" is the wrong shape for a test deliberately made assert-rather-than-skip, since any future drift in the inference (env var scoping, sys.argv contents, RLTest's own CLI shape) fails toward silence instead of noise. Re-ran the OSS_CLUSTER=1 OSS_CLUSTER_REPLICAS=1 scenario end-to-end: plain pass skips, replicas pass passes (2/2), same as before but now via an explicit declaration instead of an inference. - test_client_no_touch_resent_after_reconnect: replaced the fixed 0.15s sleep before snapshotting client ids with an active poll for the MONITOR connection to actually show up in CLIENT LIST (flags contains 'O'). conn.monitor()'s underlying socket connects lazily on first use, so a fixed delay could race on a loaded runner -- if the snapshot fired before MONITOR registered, the kill step would treat the MONITOR connection itself as "new" (looks like memtier's) and kill it. - Sanitizer-cell cost of collecting test_client_no_touch_cluster.py with no TEST: pin (asked, not code): checked a completed ASAN OSS-CLUSTER API: TCP Plaintext run -- both tests' cluster boot+run+teardown took ~15-20s combined inside a ~13m job that also runs every other OSS-CLUSTER test file. Not a meaningful driver of that cell's duration. - Trimmed PR-review-history language from env_started_with_slaves()'s docstring and the rt_no_touch snprintf comment -- kept the technical why, dropped the "an earlier version got this wrong" framing. - README: "Known limitations" (#527) is no longer a sibling of "Testing limitations" buried in the middle of the Read Preference / cluster-mode material -- promoted to its own top-level section, placed right after "Using memtier_benchmark"'s subsections and before "Crash Reporting", so it isn't nested inside content specific to a feature #527 has nothing to do with. - Generic connection-setup-command hook: still #528, no new example this round (CLIENT NO-EVICT/TRACKING/SETINFO/SETNAME/REPLY already listed). - Noted on #527 (comment, no issue-body change) that --client-no-touch widens its window slightly since NO-TOUCH arms on every connection, unlike READONLY's replica-only scope -- a marginally larger window for the same pre-existing bug, not a new instance of it. The RLTest SHA-pin split (also asked this round) is done as of the prior commit -- PR #531. Verified locally: test_client_no_touch.py 4/4 (polling fix), and the exact OSS_CLUSTER=1 OSS_CLUSTER_REPLICAS=1 scenario end-to-end (plain pass skips, replicas pass 2/2) with the new dedicated env var. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Biggest round in a while — taking each point.
Rebuilt clean, format-check clean. Verified locally: |
…e, fix stale PR description - Investigated whether the "evbuffer notify callback doesn't re-arm EPOLLOUT on the first user-level send" claim (originally written for READONLY, copied for CLIENT NO-TOUCH) actually holds for discovered connections in general, since HELLO already goes out via the identical plain evbuffer_add path on the identical connection type. Empirically: attached MONITOR to a non-seed shard in a local 3-node cluster and ran memtier --cluster-mode against the bootstrap seed only -- real SET/GET traffic flowed on the non-seed connection, which requires m_hello to have reached setup_done, meaning HELLO's plain-evbuffer-add write did flush and get acknowledged there. Also: bufferevent_enable(m_bev, EV_READ | EV_WRITE) already ran unconditionally at BEV_EVENT_CONNECTED before the original READONLY bug was even diagnosed (present before PR #456 merged). The original bug-fix commit also fixed an unrelated fill_pipeline spin that could have starved BEV_EVENT_CONNECTED from ever firing for the replica connection at all -- a plausible alternate explanation for what was actually observed. Filed #532 for someone to actually isolate the two changes and confirm which one was load-bearing, rather than resolving it unilaterally in this PR (bufferevent_write is safe regardless either way -- same wire bytes -- just possibly over-justified). Reworded the CLIENT NO-TOUCH comment to say it follows READONLY's precedent rather than independently asserting the same causal claim, and points at #532. - PR description: fixed the stale claim that this branch carries the RLTest SHA pin (it doesn't -- split to #531, landing separately) and explained the run_tests.sh MEMTIER_CLUSTER_REPLICAS_EXPECTED addition that IS still in this PR. - The benchmark_error_log NULL-arg nitpick and env_started_with_slaves() redundancy question are answered in the PR thread, not with a code change: the NULL-arg style matches the sibling AUTH/READONLY cases (consistency, not a new gap), and env_started_with_slaves() still has one real (if lower-stakes) consumer -- the defensive stderr warning in get_cluster_replica_connections(), not currently expected to fire but still useful if the fork ever does regress. Verified locally: test_client_no_touch.py 4/4. Rebuilt clean (comment-only change to shard_connection.cpp), format-check clean. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
The Empirically: attached Also checked: I don't think I should resolve this unilaterally in a feature PR — filed #532 for someone to actually isolate the two changes and confirm which one was load-bearing.
Rebuilt clean (comment-only C++ change), re-verified |
…icting comment, consistent #532 framing - Removed env_started_with_slaves() entirely. Now that MEMTIER_CLUSTER_REPLICAS_EXPECTED answers "did this invocation ask for replicas" correctly (round 28), it's the same question the sys.argv helper existed to answer -- get_cluster_replica_connections()'s warning path now reads the env var directly instead of keeping two mechanisms for one question. - peer_client_has_any_setup_in_progress()'s pre-existing top comment said "not yet ready for reads (HELLO/CLUSTER SLOTS/READONLY pending)", which contradicts the comment three lines below it (correctly stating is_ready_for_reads() doesn't gate on HELLO) -- and is_ready_for_reads()'s own definition confirms the lower comment is the accurate one. Fixed the top comment instead of leaving the two disagreeing. - Made the bufferevent_write/EPOLLOUT framing consistent at one confidence level everywhere it's told: the PR description and test_client_no_touch_cluster.py's docstring both asserted the original diagnosis as fact while the code comment (since round 29) calls it an open question tracked in #532. Reworded both to match the code: this follows READONLY's precedent, whether the precedent's diagnosis holds is open (and empirically doubtful -- HELLO already flushes fine via the plain path on the same connection type), bufferevent_write is safe either way. - #531 ordering and the test-infra-scope nitpick are answered in the PR thread: I don't have authority to merge #531 myself (branch protection requires a review, and self-approving my own PR isn't something to do unilaterally) -- that one now needs an actual human maintainer action, not another review round. Verified locally: the OSS_CLUSTER=1 OSS_CLUSTER_REPLICAS=1 scenario end-to-end again after removing env_started_with_slaves() (plain pass skips, replicas pass 2/2) -- confirms the consolidated warning-path signal didn't regress anything. Rebuilt clean, format-check clean. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Rebuilt clean, format-check clean, re-verified the |
…op redundant import, trim caller-list and #532 comments further - test_client_no_touch_resent_after_reconnect: replaced the fixed 2.0s sleep before the kill step with a poll on the already-populated `results` list for the first CLIENT NO-TOUCH ON to show up (bounded, 15s). Same class of fix as round 30's MONITOR-registration poll: this file has no TEST: pin in the asan.yml/tsan.yml/ubsan.yml OSS_STANDALONE plaintext cells, where a 2s startup budget is the tightest -- a fixed sleep firing before memtier connects left `new_ids` empty, a hard assert with no retry. - Dropped the redundant function-local `import os` in get_cluster_replica_connections() -- os is already imported at module scope, unlike the sys/redis imports next to it which genuinely aren't. - get_redis_conn_for_node()'s docstring no longer enumerates its callers by name -- it was already stale inside this same PR (test_client_no_touch.py calls it directly too, not just through test_mget_protocol.py's wrapper) and will just go stale again next time another test picks it up. - Trimmed the CLIENT NO-TOUCH block's comment in send_conn_setup_commands() further, per round 30's own ask: kept "follows READONLY's precedent, whether that precedent holds is open -- see #532 -- bufferevent_write is safe either way" and the ordering-consistency sentence, dropped the rest. #531 ordering: same answer as last round -- open, green, needs a human maintainer to actually merge it, not another review pass. Verified locally: test_client_no_touch.py 4/4 (poll-based kill wait), dedicated replica cell 2/2 (import cleanup didn't regress the warning path). Rebuilt clean, format-check clean. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Three real fixes, one more restated.
Verified locally: |
…plica-expected signals, older-server help clause - Lifted _wait_for_monitor_registered()/_client_list() out of test_client_no_touch.py into include.py (client_list()/ wait_for_monitor_registered()) and switched test_client_no_touch_cluster.py's _run_and_check_no_touch() from a flat time.sleep(0.15) to the same poll. That file is collected by the asan/tsan/ubsan cluster cells -- the slowest runners, where a missed MONITOR attach costs a red build. Needed its own control connection to the specific node being monitored (CLIENT LIST is per-server) built via get_redis_conn_for_node() from conn's host/port -- first attempt at this used redis.Redis(**conn.connection_pool.connection_kwargs), which blows up with "unexpected keyword argument 'himport_registry'" since ConnectionPool.connection_kwargs carries internal bookkeeping keys, not just constructor args; caught locally before push by re-running the OSS_CLUSTER=1 OSS_CLUSTER_REPLICAS=1 scenario end-to-end. - get_cluster_replica_connections()'s and test_client_no_touch_cluster.py's hard-fail gate now both call a shared replicas_expected() (tests/include.py), which ORs MEMTIER_CLUSTER_REPLICAS_EXPECTED with the sys.argv --use-slaves check instead of relying on the env var alone -- round 30 fully removed the argv fallback, which meant invoking RLTest directly with --use-slaves (bypassing run_tests.sh) silently degraded the hard-fail to a skip. One shared signal instead of duplicating the OR in two places. - --help/man page: added "the connection fails loudly rather than silently running with the flag doing nothing" for what happens against a pre-7.2 or rejecting server. #531 ordering and the README/#462 scope nitpick: same answers as prior rounds, nothing new to add. Verified locally: test_client_no_touch.py 4/4, and (after finding and fixing the connection_kwargs bug) the OSS_CLUSTER=1 OSS_CLUSTER_REPLICAS=1 scenario end-to-end -- plain pass 2/2 (pass + skip), replicas pass 2/2 (pass + pass). Rebuilt clean, format-check clean. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
All four, and a bug I caught while doing item 1.
Verified locally: |
No code changes this round — filed the |
Summary
Fixes #525. Adds
--client-no-touch, which sendsCLIENT NO-TOUCH ONonce per connection as a new step in the existing connection-setup ladder (alongside AUTH/SELECT/HELLO/READONLY/CLUSTER SLOTS inshard_connection.cpp) — not via--command, since that would re-send it every N ops and pollute the ops budget and latency histogram. Redis protocol only (7.2+;deps/commands_json/client-no-touch.jsonconfirmssince: 7.2.0); the CLI rejects the flag on memcache protocols, and an unsupported/erroring server fails the connection loudly via the existingreport_connection_stage_failurepath rather than silently benchmarking the wrong thing.shard_connection.h/shard_connection.cpp: newrt_no_touchrequest type andm_no_touch_stateladder member, threaded throughconnect()/disconnect()/is_conn_setup_done()/send_conn_setup_commands()/process_response()/the replay-drop list/push_req()/get_last_request_type(). Appended to the ladder after READONLY (not between HELLO and READONLY as in an earlier version of this PR) so AUTH/SELECT/HELLO/READONLY keep the relative wire order they've always had — nothing in the ladder depends on NO-TOUCH's position specifically, since it's one pipelined pass with no reply waited on in between. Sent viabufferevent_writewith the RESP bytes inline (not a protocol-levelevbuffer_addcall), following the same precedent READONLY already sets for a shard connection discovered mid-run through a liveCLUSTER SLOTSrefresh (attached viabufferevent_socket_new+ a laterbufferevent_socket_connect). Whether that precedent's original diagnosis (the first user-level send on such a connection doesn't get its EPOLLOUT armed by the plain evbuffer path) actually holds is an open question -- empirically, HELLO already flushes fine via the plain path on the same connection type, which contradicts it as a universal claim -- tracked in Verify whether bufferevent_write is actually required for READONLY/CLIENT NO-TOUCH on discovered connections, or the original diagnosis was wrong #532 rather than settled here, sincebufferevent_writeis safe either way (identical wire bytes). Unlike READONLY, NO-TOUCH arms on every connection including primaries.memtier_benchmark.h/memtier_benchmark.cpp:--client-no-touchCLI flag, redis-protocol-only validation (checked both at parse time and again at connect-arm time),--helptext,--show-config/JSON provenance fields (placed right afterprint-all-runs, before the#ifdef HAVE_EVHTTPblock, in bothconfig_print()andconfig_print_to_json()— matching howprint-all-runsitself was appended in PR#298).cluster_client.cpp:classify_read()classifiesrt_no_touchas non-read, like the other setup commands.memtier_benchmark.1,bash-completion/memtier_benchmark: documented (hand-edited rather than a fullhelp2manregen, to avoid pulling in unrelated pre-existing drift in the man page).tests/test_cli_validation_client_no_touch.py: parse-time protocol-gating tests (no live server needed).tests/test_client_no_touch.py: functional tests against a realredis-server—OBJECT IDLETIMEmust not reset under the flag (positive case, also assertingALL STATS.Gets.Count > 0so it can't pass vacuously on zero traffic) and must reset without it (negative control). A third test uses an ACL user withclient|no-touchdenied to prove the server-rejection path fails loudly, without needing an actual pre-7.2 server.tests/test_client_no_touch_cluster.py+.github/workflows/ci.yml: two tests, both actually running (not skipped) in a new dedicatedOSS-CLUSTER + replicas: client-no-touchCI cell — one confirms NO-TOUCH lands on a replica connection viaMONITOR, the other on a non-seed primary shard (any shard beyond memtier's bootstrap seed connection is reached via the identical live-discovery path, so this needs no replicas at all).tests/run_tests.sh: theOSS_CLUSTER_REPLICAS=1block now setsMEMTIER_CLUSTER_REPLICAS_EXPECTED=1inside its own subshell (not globally), so the new cluster test's hard-fail gate below is driven by a signal scoped to the specific RLTest invocation that actually requested replicas, not one that stays set across a script run that makes more than one invocation.tests/test_requirements.txt) was split out into ci: pin RLTest fork dependency to a commit SHA instead of a branch name #531, opened directly off master, so that CI-dependency change isn't gated on this PR's review. This branch does not carry that pin itself --tests/test_requirements.txthere matches master (still the branch name) until ci: pin RLTest fork dependency to a commit SHA instead of a branch name #531 merges and this branch picks it up on the next merge from master. The newOSS-CLUSTER + replicas: client-no-touchcell's hard-fail-on-missing-replicas behavior is unaffected by which of the twotests/test_requirements.txtstates is in effect; only the SHA-vs-branch-name mutation risk depends on ci: pin RLTest fork dependency to a commit SHA instead of a branch name #531 landing first.Known limitation (pre-existing, not introduced here)
Under
--retry-on-error, several places gate a send on the connection just being TCP-connected rather than on the full setup ladder being done (is_conn_setup_done()):drain_replay_queue_after_reconnect()runs beforefill_pipeline()(the only caller ofsend_conn_setup_commands()) on reconnect,handle_retry_drain_event()gates only onm_connection_state, and the cross-shard MOVED-redirect path incluster_client.cppdeliberately uses a partial readiness check. So a replayed, retried, or redirected request can beat AUTH, SELECT, HELLO, READONLY, and CLIENT NO-TOUCH on a connection that hasn't finished its ladder yet — landing on the wrong DB under--select-dbis a materially worse instance of this than one key's recency being wrong under--client-no-touch.This predates this PR and affects every setup-ladder-dependent flag, not just this one. I found and initially fixed the reconnect-replay instance, then added a
--client-no-touch-specific warning about it, but both were the wrong scope for this PR: the fix needs its own PR + regression test (reliably testing a reconnect-timing race turned out to be genuinely nontrivial — see the issue for what I tried), and a flag-specific runtime warning understated the actual affected set. Filed as #527 with the full finding, including the retry-drain and MOVED-redirect instances found later. Where to disclose #527 in-product oscillated across review rounds (a--client-no-touch-specific--help/man-page line, added, then reverted as still too narrow; a general one, added, then moved to README); landed on README's Known limitations section, positioned near the other cluster/testing caveats rather than as the first thing under "Using memtier_benchmark" — not on--client-no-touch's own--help/man-page entry, since #527 predates this flag and hits--select-dbharder.Test plan
autoreconf -ivf && ./configure && makebuild, no warnings;make format/clang-format --dry-run --Werrorclean on every touched file.redis-cli MONITORverification against real clusters (2-shard, TLS and non-TLS, with and without a real cluster-replicated replica):CLIENT NO-TOUCH ONis the first command observed on the bootstrap connection, a non-seed primary, and a replica alike.redis-server:OBJECT IDLETIMEkeeps growing under--client-no-touchGET traffic, resets without it, and the ACL-rejection path aborts the run with a clearCLIENT NO-TOUCH failedmessage and non-zero exit.--client-no-touchrejected with a clear error on-P memcache_text/memcache_binary.test_cli_validation_client_no_touch.py,test_client_no_touch.py,test_client_no_touch_cluster.py) pass under RLTest, standalone and cluster (including the new dedicated CI matrix cell).--help/--show-config/CLI-validation behavior vsorigin/masterwhen the flag is unset, aside from the additiveclient-no-touchconfig-echo key; disassembly check found no added instructions on the request-processing dispatch path when the flag is off (is_conn_setup_done()does carry one more&&term now, a real but trivial addition at its one call site).--reconnect-on-error+--client-no-touchunder repeatedCLIENT KILL): clean completion, no hang.Note
Medium Risk
Changes the per-connection setup ladder and readiness gating on a hot path when the flag is enabled; default-off behavior is preserved, but mis-timed sends under
--retry-on-errorremain a documented pre-existing risk (#527).Overview
Adds
--client-no-touch, a Redis-protocol-only flag that sendsCLIENT NO-TOUCH ONonce per connection as a new step in the existing setup ladder (after READONLY, before CLUSTER SLOTS), so benchmark traffic does not refresh key LRU/LFU recency. Rejected servers or ACL denials fail the connection via the same stage-failure path as other ladder steps; memcache protocols are rejected at CLI parse time.Implementation extends
shard_connectionwithrt_no_touch/m_no_touch_state(included inis_conn_setup_done(), reconnect re-arm, replay drops), usesbufferevent_writefor the wire bytes (including cluster-discovered shards), and treats the step as non-read incluster_client. Config surfaces through--show-config, JSON output, man page, and bash completion.Tests and CI: new RLTest modules for CLI validation, standalone behavior (
OBJECT IDLETIME, loud failure, reconnect MONITOR), and cluster coverage (replica + non-seed primary); a dedicatedOSS-CLUSTER + replicas: client-no-touchmatrix job; shared helpers intests/include.pyandMEMTIER_CLUSTER_REPLICAS_EXPECTEDscoping inrun_tests.sh. README documents the pre-existing--retry-on-errorsetup-ladder race (#527) and revises read-preference testing limitations.Reviewed by Cursor Bugbot for commit 9c31edc. Bugbot is set up for automated code reviews on this repo. Configure here.