Fix cluster pipeline deadlock on connection acquisition#4564
Open
lh0156 wants to merge 3 commits into
Open
Conversation
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Reviewed by Cursor Bugbot for commit c0e7984. Configure here.
Collaborator
|
@lh0156 |
b812409 to
b0120ce
Compare
Collaborator
|
Not sure there is a reliable way of detecting a deadlock. I think having a dedicated client with a configured |
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 #4557.
Problem
I added deterministic coverage for the cluster pipeline deadlock condition described in #4557.
The problematic condition is:
If each node pool has no immediately available connection, both pipelines can block before
sync()while still holding previously borrowed node connections with pending responses. This creates a hold-and-wait cycle.Options considered
I considered a few possible approaches:
I chose the fail-fast approach because it is the smallest change that directly breaks the deadlock condition. Buffering would change the eager-write behavior of pipeline commands, and drain/release would make partial response handling significantly more complex.
Fix
This does not make all cluster pipeline acquisition non-blocking. The first node acquisition keeps the existing behavior, because the pipeline does not hold any node connection yet.
For subsequent node acquisitions,
ClusterPipelinenow uses a zero-wait acquisition path. If the new node connection is not immediately available, the pipeline fails fast instead of waiting while holding another node connection.When this happens, the already-held connections are marked broken before being closed, because commands may already have been written and their responses may still be pending. Returning those connections as healthy could contaminate the pool.
Why this prevents the deadlock
The deadlock requires a pipeline to hold one node connection while waiting for another node connection.
This change removes that condition from the cluster pipeline path:
So two pipelines can no longer wait on each other while each keeps a different node connection checked out.
Tests
Added focused unit coverage for:
sync()close()not attempting to read pending responses after fail-fast cleanupConnectionPool#getResource(Duration.ZERO)failing fast when exhaustedVerified locally with:
mvn -Dtest=MultiNodePipelineBaseDeadlockTest testResult:
Note
Medium Risk
Changes connection acquisition semantics for multi-shard cluster pipelines—pools may see more immediate failures under contention—but behavior is scoped to pipeline use and includes explicit error guidance and broken-connection handling.
Overview
Fixes cluster pipeline deadlocks (#4557) when concurrent pipelines each hold one shard connection and then block borrowing another.
MultiNodePipelineBaseonly allows blocking pool borrow for the first node in a pipeline; further nodes go throughgetConnection(nodeKey, allowBlocking). On non-blocking acquisition failure it clears held connections, marking them broken when responses are still pending so pools are not poisoned.ClusterPipelineuses normal acquisition for the first node andDuration.ZEROborrows for additional nodes viaClusterConnectionProvider, surfacing aJedisClusterOperationExceptionwith deadlock/pool tuning guidance when no connection is immediately available.Pool/ConnectionPoolgaingetResource(Duration)to support bounded waits.MultiNodePipelineBaseDeadlockTestdocuments the old circular-wait behavior and asserts the new policy end-to-end.Reviewed by Cursor Bugbot for commit b0120ce. Bugbot is set up for automated code reviews on this repo. Configure here.