-
Notifications
You must be signed in to change notification settings - Fork 2.6k
chore(refactor): move per-connection CLIENT commands to statefulCmdable #3961
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
11
commits into
master
Choose a base branch
from
feature/stateful-conn-commands
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
11 commits
Select commit
Hold shift + click to select a range
b38154d
feat: move per-connection CLIENT commands to statefulCmdable
ndyakov 335ee05
test: pin both rejection layers for CLIENT TRACKING on a CSC client
ndyakov 05634f6
test(command): gate dedicated CLIENT TRACKING spec
ndyakov fadbf2c
fix(client): preserve tracking args in pooled ClientTracking wrappers
ndyakov 4198290
fix(client): reject per-connection state commands in pooled pipelines
ndyakov 244a134
fix(pipeline): surface pooled state-command rejection through Exec
ndyakov b55e0f9
ci(govulncheck): use stable Go to pick up security patches
ndyakov 23f6ed4
fix(pipeline): enforce pooled-state rejection on cluster pipelines
ndyakov 57d18a9
fix(pipeline): enforce pooled-state rejection on Ring pipelines
ndyakov cdafb4e
chore(pipeline): compact review-round comments
ndyakov 3b6008a
fix(pipeline): CSC/pooled error agreement; Clone keeps stateRejected
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,168 @@ | ||
| package redis | ||
|
|
||
| import ( | ||
| "context" | ||
| "errors" | ||
| "reflect" | ||
| "testing" | ||
| ) | ||
|
|
||
| // TestPooledClientTrackingPreservesArgs verifies the pooled-client CLIENT | ||
| // TRACKING / MAINT_NOTIFICATIONS wrappers build the full argument list (mirroring | ||
| // the stateful command) before failing with guidance, instead of dropping the | ||
| // caller's options (#3961). The command is pre-failed without dispatch, so no | ||
| // server is needed. | ||
| func TestPooledClientTrackingPreservesArgs(t *testing.T) { | ||
| ctx := context.Background() | ||
| c := NewClient(&Options{Addr: ":6379"}) | ||
| defer c.Close() | ||
|
|
||
| opt := &ClientTrackingOptions{Redirect: 42, Bcast: true, Prefixes: []string{"foo"}, NoLoop: true} | ||
|
|
||
| cases := []struct { | ||
| name string | ||
| cmd *StatusCmd | ||
| err error | ||
| want []interface{} | ||
| }{ | ||
| { | ||
| name: "ClientTrackingOn", | ||
| cmd: c.ClientTrackingOn(ctx, opt), | ||
| err: errClientTrackingOnPooledClient, | ||
| want: []interface{}{"client", "tracking", "on", "redirect", int64(42), "bcast", "prefix", "foo", "noloop"}, | ||
| }, | ||
| { | ||
| name: "ClientTracking(on)", | ||
| cmd: c.ClientTracking(ctx, true, opt), | ||
| err: errClientTrackingOnPooledClient, | ||
| want: []interface{}{"client", "tracking", "on", "redirect", int64(42), "bcast", "prefix", "foo", "noloop"}, | ||
| }, | ||
| { | ||
| name: "ClientMaintNotifications(on)", | ||
| cmd: c.ClientMaintNotifications(ctx, true, "external"), | ||
| err: errClientMaintNotificationsOnPooledClient, | ||
| want: []interface{}{"client", "maint_notifications", "on", "moving-endpoint-type", "external"}, | ||
| }, | ||
| { | ||
| name: "ClientMaintNotifications(off)", | ||
| cmd: c.ClientMaintNotifications(ctx, false, ""), | ||
| err: errClientMaintNotificationsOnPooledClient, | ||
| want: []interface{}{"client", "maint_notifications", "off"}, | ||
| }, | ||
| } | ||
| for _, tc := range cases { | ||
| t.Run(tc.name, func(t *testing.T) { | ||
| if !errors.Is(tc.cmd.Err(), tc.err) { | ||
| t.Fatalf("Err() = %v, want %v", tc.cmd.Err(), tc.err) | ||
| } | ||
| if got := tc.cmd.Args(); !reflect.DeepEqual(got, tc.want) { | ||
| t.Fatalf("Args() = %v, want %v (options dropped)", got, tc.want) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| // TestPooledClientMaintNotificationsDefaultEndpoint verifies the empty endpoint | ||
| // type defaults to "none", matching the stateful command. | ||
| func TestPooledClientMaintNotificationsDefaultEndpoint(t *testing.T) { | ||
| c := NewClient(&Options{Addr: ":6379"}) | ||
| defer c.Close() | ||
| cmd := c.ClientMaintNotifications(context.Background(), true, "") | ||
| want := []interface{}{"client", "maint_notifications", "on", "moving-endpoint-type", "none"} | ||
| if got := cmd.Args(); !reflect.DeepEqual(got, want) { | ||
| t.Fatalf("Args() = %v, want %v", got, want) | ||
| } | ||
| } | ||
|
|
||
| // TestPipelineRejectsStateCommandsWhenPooled verifies per-connection state | ||
| // commands (CLIENT TRACKING / MAINT_NOTIFICATIONS) are rejected in a POOLED | ||
| // pipeline — whose borrowed connection returns to the pool after Exec — but | ||
| // allowed (queued) in a pipeline from a dedicated *Conn (#3961). No server needed: | ||
| // rejections are pre-failed, and the Conn pipeline only queues. | ||
| func TestPipelineRejectsStateCommandsWhenPooled(t *testing.T) { | ||
| ctx := context.Background() | ||
| // localhost:1 is never dialed: the pooled-pipeline guard in | ||
| // generalProcessPipeline fires before any connection is acquired. | ||
| c := NewClient(&Options{Addr: "localhost:1"}) | ||
| defer c.Close() | ||
|
|
||
| // Pooled pipeline: the returned command carries the guidance error AND the | ||
| // command is queued, so Exec surfaces the rejection even when the caller | ||
| // ignores the returned command. The command must never be sent to a borrowed | ||
| // pooled connection. | ||
| pp := c.Pipeline() | ||
| if cmd := pp.ClientTrackingOn(ctx, &ClientTrackingOptions{Bcast: true}); !errors.Is(cmd.Err(), errClientTrackingOnPooledClient) { | ||
| t.Fatalf("pooled Pipeline ClientTrackingOn err = %v, want errClientTrackingOnPooledClient", cmd.Err()) | ||
| } | ||
| if cmd := pp.ClientTrackingOff(ctx); !errors.Is(cmd.Err(), errClientTrackingOnPooledClient) { | ||
| t.Fatalf("pooled Pipeline ClientTrackingOff err = %v, want reject", cmd.Err()) | ||
| } | ||
| if cmd := pp.ClientMaintNotifications(ctx, true, "none"); !errors.Is(cmd.Err(), errClientMaintNotificationsOnPooledClient) { | ||
| t.Fatalf("pooled Pipeline ClientMaintNotifications err = %v, want reject", cmd.Err()) | ||
| } | ||
| if pp.Len() != 3 { | ||
| t.Fatalf("pooled Pipeline queued %d state commands, want 3 (queued so Exec surfaces the rejection)", pp.Len()) | ||
| } | ||
| // Exec surfaces the rejection through the guard, before any dial (this would | ||
| // otherwise fail dialing localhost:1 with a different error). | ||
| if _, err := pp.Exec(ctx); !errors.Is(err, errClientTrackingOnPooledClient) { | ||
| t.Fatalf("pooled Pipeline Exec err = %v, want errClientTrackingOnPooledClient (guarded before dial)", err) | ||
| } | ||
|
|
||
| // The common Pipelined pattern — callback ignores the returned command — must | ||
| // still fail rather than silently reporting success (the dropped-error bug). | ||
| if _, err := c.Pipelined(ctx, func(pipe Pipeliner) error { | ||
| pipe.ClientTrackingOff(ctx) | ||
| return nil | ||
| }); !errors.Is(err, errClientTrackingOnPooledClient) { | ||
| t.Fatalf("Pipelined ClientTrackingOff err = %v, want errClientTrackingOnPooledClient", err) | ||
| } | ||
|
|
||
| // Discard drops queued rejections like any other queued command. | ||
| pp2 := c.Pipeline() | ||
| pp2.ClientTrackingOff(ctx) | ||
| pp2.Discard() | ||
| if _, err := pp2.Exec(ctx); err != nil { | ||
| t.Fatalf("after Discard, Exec err = %v, want nil (empty pipeline)", err) | ||
| } | ||
|
|
||
| // Dedicated-Conn pipeline: sticky → allowed → queued (state stays on the conn). | ||
| conn := c.Conn() | ||
| defer conn.Close() | ||
| cp := conn.Pipeline() | ||
| if cmd := cp.ClientTrackingOn(ctx, &ClientTrackingOptions{Bcast: true}); errors.Is(cmd.Err(), errClientTrackingOnPooledClient) { | ||
| t.Fatalf("Conn Pipeline wrongly rejected ClientTrackingOn: %v", cmd.Err()) | ||
| } | ||
| if cp.Len() != 1 { | ||
| t.Fatalf("Conn Pipeline queued %d, want 1 (state command should be queued on a dedicated conn)", cp.Len()) | ||
| } | ||
| } | ||
|
|
||
| // TestTxPipelineAllowsStateCommands pins that a Tx pipeline is sticky: WATCH | ||
| // pins one connection for the whole Tx, so CLIENT TRACKING queues there instead | ||
| // of being rejected as pooled (#3961 regression flagged by review). Needs a | ||
| // server because Watch dials. | ||
| func TestTxPipelineAllowsStateCommands(t *testing.T) { | ||
| ctx := context.Background() | ||
| c := NewClient(&Options{Addr: ":6379"}) | ||
| defer c.Close() | ||
| if err := c.Ping(ctx).Err(); err != nil { | ||
| t.Skipf("no redis: %v", err) | ||
| } | ||
|
|
||
| err := c.Watch(ctx, func(tx *Tx) error { | ||
| for _, p := range []Pipeliner{tx.Pipeline(), tx.TxPipeline()} { | ||
| cmd := p.ClientTrackingOn(ctx, nil) | ||
| if errors.Is(cmd.Err(), errClientTrackingOnPooledClient) { | ||
| t.Fatalf("Tx pipeline wrongly rejected CLIENT TRACKING (should be sticky): %v", cmd.Err()) | ||
| } | ||
| if p.Len() != 1 { | ||
| t.Fatalf("Tx pipeline queued %d, want 1 (state command allowed on the pinned conn)", p.Len()) | ||
| } | ||
| } | ||
| return nil | ||
| }) | ||
| if err != nil { | ||
| t.Fatalf("Watch: %v", err) | ||
| } | ||
| } |
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
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.