You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
shard_connection::handle_event()'s BEV_EVENT_CONNECTED handler calls drain_replay_queue_after_reconnect() (replays in-flight requests that survived a disconnect) beforefill_pipeline() gets a chance to re-send the connection-setup ladder (AUTH/SELECT/HELLO/READONLY/CLUSTER SLOTS) on the fresh connection:
// After (re)connect: replay any in-flight requests that survived the// disconnect. This must happen *before* fill_pipeline() so the old// requests get back on the wire first; otherwise pipeline ordering// would shuffle replayed work behind fresh work.if (m_config->retry_on_error && m_replay_queue && !m_replay_queue->empty()) {
drain_replay_queue_after_reconnect();
}
Since a fresh TCP connection has none of that connection-scoped server-side state yet, and redis processes a pipelined connection strictly in receipt order, a replayed request can reach the server before AUTH (or SELECT/HELLO/READONLY) is even sent on that connection — under --authenticate + --retry-on-error + --reconnect-on-error, this means a replayed command can get rejected with -NOAUTH on a connection that will authenticate a moment later, silently corrupting that one request's result (or its retry accounting) instead of cleanly failing.
While implementing --client-no-touch, I found and initially fixed this by moving the setup-ladder send ahead of the replay drain (shard_connection.cpp), and it was called out as worth its own PR + dedicated test coverage during review of #526 (see that PR's history/comments — the fix was reverted out of #526 to keep that PR focused on the --client-no-touch feature itself).
What I found trying to write a regression test
This is genuinely tricky to test reliably:
A CLIENT-KILL-churn stress test (matching tests/test_reconnections.py's pattern) against a password-protected server, with --retry-on-error --reconnect-on-error, can reproduce the bug via a MONITOR capture (grouping commands by source address:port and asserting the first command from any connection is always AUTH) — but only probabilistically; it depends on catching a kill at the exact moment a request is genuinely in-flight.
More aggressive kill intervals (~0.1–0.4s) reliably trigger some kind of stall/non-completion within the benchmark's --test-time window, but I found this happens under both the buggy and the fixed code at that aggressivenes, so a "does it hang" signal doesn't actually discriminate between them — it's a separate, orthogonal reconnect-storm-robustness question, not proof of this specific ordering bug.
A fully deterministic reproduction (guaranteeing something is in-flight at the exact moment of a forced disconnect, without relying on network-timing luck against a live CLIENT KILL) would likely need either a fault-injection hook in the client itself, or a way to pause the C++ event loop at a known point — more invasive than a black-box Python test can do cleanly today.
Suggested approach
Re-apply the ordering fix (send the setup ladder before draining the replay queue on reconnect) — verified safe and effective via manual redis-cli MONITOR testing in Support CLIENT NO-TOUCH as a connection-setup command #526's history.
Get a reliable regression test in place — possibly by:
Increasing pipeline depth / in-flight window further and running many more kill iterations for better odds, accepting some probabilistic slack (matching the rigor bar from PR#364, adapted for a timing-dependent bug), and/or
A cheaper, more targeted unit-level test if there's a lower-level hook into shard_connection's reconnect path that doesn't require racing a live process.
Happy to pick this up; flagging now so it's tracked rather than lost in #526's review thread.
Summary
shard_connection::handle_event()'sBEV_EVENT_CONNECTEDhandler callsdrain_replay_queue_after_reconnect()(replays in-flight requests that survived a disconnect) beforefill_pipeline()gets a chance to re-send the connection-setup ladder (AUTH/SELECT/HELLO/READONLY/CLUSTER SLOTS) on the fresh connection:Since a fresh TCP connection has none of that connection-scoped server-side state yet, and redis processes a pipelined connection strictly in receipt order, a replayed request can reach the server before AUTH (or SELECT/HELLO/READONLY) is even sent on that connection — under
--authenticate+--retry-on-error+--reconnect-on-error, this means a replayed command can get rejected with-NOAUTHon a connection that will authenticate a moment later, silently corrupting that one request's result (or its retry accounting) instead of cleanly failing.Discovered while working on #525 / PR #526
While implementing
--client-no-touch, I found and initially fixed this by moving the setup-ladder send ahead of the replay drain (shard_connection.cpp), and it was called out as worth its own PR + dedicated test coverage during review of #526 (see that PR's history/comments — the fix was reverted out of #526 to keep that PR focused on the--client-no-touchfeature itself).What I found trying to write a regression test
This is genuinely tricky to test reliably:
tests/test_reconnections.py's pattern) against a password-protected server, with--retry-on-error --reconnect-on-error, can reproduce the bug via a MONITOR capture (grouping commands by sourceaddress:portand asserting the first command from any connection is always AUTH) — but only probabilistically; it depends on catching a kill at the exact moment a request is genuinely in-flight.--test-timewindow, but I found this happens under both the buggy and the fixed code at that aggressivenes, so a "does it hang" signal doesn't actually discriminate between them — it's a separate, orthogonal reconnect-storm-robustness question, not proof of this specific ordering bug.Suggested approach
redis-cli MONITORtesting in Support CLIENT NO-TOUCH as a connection-setup command #526's history.shard_connection's reconnect path that doesn't require racing a live process.Happy to pick this up; flagging now so it's tracked rather than lost in #526's review thread.