Repository navigation
fix: dial peers outside the router lock and honour caller context - #277
Merged
Merged
Conversation
The router dialed a peer while holding its router-wide write lock, with a blocking dial of up to ConnectionTimeout (5s). One unreachable peer, the normal state of a node mid-restart, therefore stalled every remote Put, Get and Delete to every other node behind that dial, for up to four attempts per request until the circuit breaker opened. Dials now run under a per-node lock only: Router.mu guards just the clients map, callers for the same node share one attempt, and callers for other nodes are unaffected. A dial that lands after RemoveClient or RefreshConnections dropped the node is closed rather than leaked. Route also had no context: retries slept with time.Sleep and the dial was bound to context.Background, so a request the client had already abandoned kept dialing and backing off against the owner. RouteContext binds both to the caller's context and the remote paths in server/operations and the streaming forwarders pass theirs. An abandoned dial is not counted against the node's circuit breaker. Route and RouteWithRetry keep their signatures. Perfloop cases case_vzhjv4f9s4 and case_yf8b86gxc0. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YCoN4Him5ZY4hZovVp37ra Signed-off-by: Ovais Tariq <ot@tigrisdata.com>
efirs
previously approved these changes
Sep 26, 2026
|
…nder ctx Review follow-ups on the dial-outside-the-lock change. A second caller for a node whose dial was already in flight blocked on the node's dial mutex without regard to its own deadline, so it could wait out the full connection timeout behind the first caller. The per-node lock is now a one-slot semaphore taken with a select on ctx.Done, so a waiter returns its own context error as soon as its deadline passes. GetClientForNode, which the cluster-wide List fan-out uses, now takes the request context so a cancelled List does not wait for a blackholed peer's dial either. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YCoN4Him5ZY4hZovVp37ra Signed-off-by: Ovais Tariq <ot@tigrisdata.com>
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.
Problem
Router.getClientdialed a peer while holding the router-wide write lock, and the dial is blocking with a 5 sConnectionTimeout. One unreachable peer, which is the normal state of a node mid-restart, stalled every remote Put, Get and Delete to every other node behind that dial. With the default retry budget a single request could hold the lock for four dials before the circuit breaker opened. Rolling deploys hit this on every node restart.Routealso took no context. Retries slept withtime.Sleepand the dial usedcontext.Background, so a request the client had already abandoned kept dialing and backing off against the owner.Change
Router.munow guards only the clients map and is never held across a dial. Each node'sclientStatecarries its owndialMu; callers for the same node share one attempt, callers for other nodes are unaffected.conn/clientare read and swapped under the state's own mutex.RemoveClientorRefreshConnectionsdropped the node closes its connection instead of leaking it.RouteContext(ctx, key)onRouter,CoordinatorandOperationsbinds the retry backoff and the dial to the caller's context. The remote paths inserver/operationsand the streaming forwarders inserver/servicepass theirs.RouteandRouteWithRetrykeep their signatures.Tests
Three new tests in
coordinator/router_dial_test.go, using a context dialer that blackholes one address:DeadlineExceededpromptly and the node's failure count stays 0;make lint,make test-coordinator,make test-serverandmake test-race TESTRUN=Routerpass.Closes perfloop cases
case_vzhjv4f9s4andcase_yf8b86gxc0.🤖 Generated with Claude Code
https://claude.ai/code/session_01YCoN4Him5ZY4hZovVp37ra
Note
Medium Risk
Changes core cluster routing concurrency and connection lifecycle; mistakes could cause connection leaks, incorrect circuit-breaker behavior, or subtle routing races, though behavior is covered by targeted tests.
Overview
Fixes cluster-wide forwarding stalls during rolling restarts by not holding
Router.muacross peer dials. Each node’sclientStateuses a per-node dial semaphore so one slow/unreachable peer no longer blocks routing to every other node; concurrent callers to the same node share one dial attempt.Adds
RouteContextonRouter,Coordinator, andOperationsso retry backoff and gRPC dials respect the caller’s deadline/cancellation. Remote operations and streaming forwarders now pass request context instead ofRoutewithcontext.Background. Abandoned dials return the caller’s context error and do not increment the peer’s circuit breaker; dials that finish after a node was removed from the ring are closed instead of leaked.New tests in
router_dial_test.gocover non-blocking routing to healthy nodes, context-cancelled dials, dial-slot waiters, and cancelled retry backoff.Reviewed by Cursor Bugbot for commit b036fb7. Bugbot is set up for automated code reviews on this repo. Configure here.