-
Notifications
You must be signed in to change notification settings - Fork 2.6k
perf(autopipeline): better pool utilization on stranglers - *Client #3962
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
ndyakov
wants to merge
10
commits into
master
Choose a base branch
from
ndyakov/fix-ap-straggler-hold-pool-gate-3
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 7 commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
dddab02
perf(autopipeline): bound the straggler-hold when the pipeline pool h…
ndyakov 2e9112a
perf(autopipeline): add getPipelinePool accessor so the straggler gat…
ndyakov 3bbd0fa
fix(autopipeline): honor MaxActiveConns in straggler-hold gate
ndyakov b48e459
fix(autopipeline): tighten straggler gate; route solo via pipeline pool
ndyakov 1773fa2
fix(autopipeline): tighten free-capacity gate; keep CSC on solo flush
ndyakov 519a4d5
fix(autopipeline): keep non-CSC cacheable solos on the pipeline pool
ndyakov 091b606
ci(govulncheck): use stable Go to pick up security patches
ndyakov 7dc4d3f
chore(autopipeline): compact review-round comments
ndyakov 11f1607
fix(autopipeline): breaker-aware capacity; live CSC solo gate
ndyakov 63bef1d
fix(pool): check semaphore turns before idle in HasFreeCapacity
ndyakov File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,161 @@ | ||
| package pool_test | ||
|
|
||
| import ( | ||
| "context" | ||
| "testing" | ||
| "time" | ||
|
|
||
| "github.com/redis/go-redis/v9/internal/pool" | ||
| ) | ||
|
|
||
| // TestHasFreeCapacityHonorsMaxActiveConns pins the branch the plain | ||
| // IdleLen()>0 || Len()<Size() heuristic missed: with MaxActiveConns < PoolSize | ||
| // and no idle connection, the pool cannot serve another Get (newConn returns | ||
| // ErrPoolExhausted once poolSize >= MaxActiveConns), yet Len() < Size() still | ||
| // holds. HasFreeCapacity must report false there — that is what makes the | ||
| // autopipeline straggler-hold gate stop shortening the hold into a pool the | ||
| // flush's Get would exhaust (#3962). | ||
| func TestHasFreeCapacityHonorsMaxActiveConns(t *testing.T) { | ||
| connPool := pool.NewConnPool(&pool.Options{ | ||
| Dialer: dummyDialer, | ||
| PoolSize: 4, // Len()<Size() alone would report "free"... | ||
| MaxActiveConns: 1, // ...but only one active connection is allowed. | ||
| MaxConcurrentDials: 1, | ||
| PoolTimeout: time.Second, | ||
| DialTimeout: time.Second, | ||
| ConnMaxIdleTime: -1, | ||
| }) | ||
| t.Cleanup(func() { _ = connPool.Close() }) | ||
| ctx := context.Background() | ||
|
|
||
| // Fresh pool: nothing dialed yet, below PoolSize and MaxActiveConns — the | ||
| // first Get can dial, so there is free capacity. | ||
| if !connPool.HasFreeCapacity() { | ||
| t.Fatal("fresh pool: HasFreeCapacity() = false, want true (first Get can dial)") | ||
| } | ||
|
|
||
| first, err := connPool.Get(ctx) | ||
| if err != nil { | ||
| t.Fatalf("first Get: %v", err) | ||
| } | ||
|
|
||
| // One active connection (== MaxActiveConns) and none idle: the next Get would | ||
| // return ErrPoolExhausted even though Len()(=1) < Size()(=4). The old heuristic | ||
| // would wrongly report free; HasFreeCapacity must report false. | ||
| if connPool.HasFreeCapacity() { | ||
| t.Fatal("at MaxActiveConns with no idle conn: HasFreeCapacity() = true, want false") | ||
| } | ||
|
|
||
| // Return it: an idle connection is now ready, so there is capacity again. | ||
| connPool.Put(ctx, first) | ||
| if !connPool.HasFreeCapacity() { | ||
| t.Fatal("with an idle conn available: HasFreeCapacity() = false, want true") | ||
| } | ||
| } | ||
|
|
||
| // TestHasFreeCapacityWithoutMaxActiveConns confirms that with MaxActiveConns | ||
| // unset HasFreeCapacity reduces EXACTLY to the prior `IdleLen()>0 || Len()<Size()` | ||
| // heuristic: a fresh pool is free, a pool grown to PoolSize with no idle conn is | ||
| // reported not-free, and an idle conn makes it free again. This is a conservative | ||
| // gate, not an admission check: a second Get on the PoolSize-full pool would in | ||
| // fact still succeed with a non-pooled connection (PoolSize does not hard-block | ||
| // dials) — HasFreeCapacity deliberately does not track that, it just keeps the | ||
| // straggler-hold conservative. | ||
| func TestHasFreeCapacityWithoutMaxActiveConns(t *testing.T) { | ||
| connPool := pool.NewConnPool(&pool.Options{ | ||
| Dialer: dummyDialer, | ||
| PoolSize: 1, | ||
| MaxConcurrentDials: 1, | ||
| PoolTimeout: time.Second, | ||
| DialTimeout: time.Second, | ||
| ConnMaxIdleTime: -1, | ||
| }) | ||
| t.Cleanup(func() { _ = connPool.Close() }) | ||
| ctx := context.Background() | ||
|
|
||
| if !connPool.HasFreeCapacity() { | ||
| t.Fatal("fresh pool: HasFreeCapacity() = false, want true") | ||
| } | ||
|
|
||
| cn, err := connPool.Get(ctx) | ||
| if err != nil { | ||
| t.Fatalf("Get: %v", err) | ||
| } | ||
| // Grown to PoolSize (1), no idle conn and no free turn (the one turn is held): | ||
| // the gate reports false. | ||
| if connPool.HasFreeCapacity() { | ||
| t.Fatal("at PoolSize with no idle conn / no free turn: HasFreeCapacity() = true, want false") | ||
| } | ||
| connPool.Put(ctx, cn) | ||
| if !connPool.HasFreeCapacity() { | ||
| t.Fatal("with a usable idle conn available: HasFreeCapacity() = false, want true") | ||
| } | ||
| } | ||
|
|
||
| // TestHasFreeCapacityExcludesUnusableIdle pins the refinement that an idle | ||
| // connection which is not usable (mid handoff / re-auth) does NOT count as | ||
| // capacity — the old IdleLen()>0 term would have wrongly reported free. | ||
| func TestHasFreeCapacityExcludesUnusableIdle(t *testing.T) { | ||
| connPool := pool.NewConnPool(&pool.Options{ | ||
| Dialer: dummyDialer, | ||
| PoolSize: 1, | ||
| MaxConcurrentDials: 1, | ||
| PoolTimeout: time.Second, | ||
| DialTimeout: time.Second, | ||
| ConnMaxIdleTime: -1, | ||
| }) | ||
| t.Cleanup(func() { _ = connPool.Close() }) | ||
| ctx := context.Background() | ||
|
|
||
| cn, err := connPool.Get(ctx) | ||
| if err != nil { | ||
| t.Fatalf("Get: %v", err) | ||
| } | ||
| connPool.Put(ctx, cn) // one usable idle conn | ||
| if !connPool.HasFreeCapacity() { | ||
| t.Fatal("usable idle conn: HasFreeCapacity() = false, want true") | ||
| } | ||
|
|
||
| // Mark the idle conn unusable (as a handoff / re-auth would). It is still in | ||
| // idleConns, so IdleLen()>0, but it cannot serve a Get; at PoolSize there is | ||
| // also nothing to dial, so capacity must read false. | ||
| cn.SetUsable(false) | ||
| if connPool.HasFreeCapacity() { | ||
| t.Fatal("idle conn is UNUSABLE and pool is at PoolSize: HasFreeCapacity() = true, want false") | ||
| } | ||
| } | ||
|
|
||
| // TestHasFreeCapacityExcludesHandoffIdle pins the OnGet-reject refinement (#3962): | ||
| // an idle connection marked ShouldHandoff is still StateIdle/usable, but an OnGet | ||
| // hook (maintnotifications) diverts it to handoff instead of serving it, so | ||
| // HasFreeCapacity must not count it as capacity. With the pool at PoolSize (no | ||
| // dial possible), a lone handoff-marked idle conn means no free capacity. | ||
| func TestHasFreeCapacityExcludesHandoffIdle(t *testing.T) { | ||
| connPool := pool.NewConnPool(&pool.Options{ | ||
| Dialer: dummyDialer, | ||
| PoolSize: 1, | ||
| MaxActiveConns: 1, | ||
| MaxConcurrentDials: 1, | ||
| PoolTimeout: time.Second, | ||
| DialTimeout: time.Second, | ||
| ConnMaxIdleTime: -1, | ||
| }) | ||
| t.Cleanup(func() { _ = connPool.Close() }) | ||
| ctx := context.Background() | ||
|
|
||
| cn, err := connPool.Get(ctx) | ||
| if err != nil { | ||
| t.Fatalf("Get: %v", err) | ||
| } | ||
| if err := cn.MarkForHandoff("new-endpoint:6379", 1); err != nil { | ||
| t.Fatalf("MarkForHandoff: %v", err) | ||
| } | ||
| if !cn.IsUsable() { | ||
| t.Fatal("precondition: a handoff-marked conn should still be IsUsable (StateIdle)") | ||
| } | ||
| connPool.Put(ctx, cn) | ||
|
|
||
| if connPool.HasFreeCapacity() { | ||
| t.Fatal("HasFreeCapacity() = true with only a handoff-marked idle conn — an OnGet hook would divert it, so it must not count") | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.