Skip to content
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

balancer: make sure non-nil done returned by Pick is called #2688

Merged
merged 2 commits into from
Mar 19, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions balancer/balancer.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,8 +215,10 @@ type Picker interface {
//
// If a SubConn is returned:
// - If it is READY, gRPC will send the RPC on it;
// - If it is not ready, or becomes not ready after it's returned, gRPC will block
// until UpdateBalancerState() is called and will call pick on the new picker.
// - If it is not ready, or becomes not ready after it's returned, gRPC will
// block until UpdateBalancerState() is called and will call pick on the
// new picker. The done function returned from Pick(), if not nil, will be
// called with nil error, no bytes sent and no bytes received.
//
// If the returned error is not nil:
// - If the error is ErrNoSubConnAvailable, gRPC will block until UpdateBalancerState()
Expand Down
8 changes: 8 additions & 0 deletions picker_wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,14 @@ func (bp *pickerWrapper) pick(ctx context.Context, failfast bool, opts balancer.
}
return t, done, nil
}
if done != nil {
done(balancer.DoneInfo{
Err: nil,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are all the defaults - maybe delete and a comment "default DoneInfo indicates RPC did not occur"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the field was named NoBytesSent, we would want to set it to true (It's great that's not the case :) )

Deleted and explain in a comment.

Copy link
Contributor Author

@menghanl menghanl Mar 14, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't test this because returning a non-Ready SubConn from picker is very racy.

Trailer: nil,
BytesSent: false,
BytesReceived: false,
})
}
grpclog.Infof("blockingPicker: the picked transport is not ready, loop back to repick")
// If ok == false, ac.state is not READY.
// A valid picker always returns READY subConn. This means the state of ac
Expand Down
24 changes: 23 additions & 1 deletion test/balancer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ package test
import (
"context"
"reflect"
"sync"
"testing"
"time"

Expand Down Expand Up @@ -103,10 +104,10 @@ type picker struct {
}

func (p *picker) Pick(ctx context.Context, opts balancer.PickOptions) (balancer.SubConn, func(balancer.DoneInfo), error) {
p.bal.pickOptions = append(p.bal.pickOptions, opts)
if p.err != nil {
return nil, nil, p.err
}
p.bal.pickOptions = append(p.bal.pickOptions, opts)
return p.sc, func(d balancer.DoneInfo) { p.bal.doneInfo = append(p.bal.doneInfo, d) }, nil
}

Expand Down Expand Up @@ -184,4 +185,25 @@ func testPickAndDone(t *testing.T, e env) {
if len(b.doneInfo) < 2 || !reflect.DeepEqual(b.doneInfo[1].Trailer, testTrailerMetadata) {
t.Fatalf("b.doneInfo = %v; want b.doneInfo[1].Trailer = %v", b.doneInfo, testTrailerMetadata)
}
if len(b.pickOptions) != len(b.doneInfo) {
t.Fatalf("Got %d picks, but %d doneInfo, want equal amount", len(b.pickOptions), len(b.doneInfo))
}
// To test done() is always called, even if it's returned with a non-Ready
// SubConn.
//
// Stop server and at the same time send RPCs. There are chances that picker
// is not updated in time, causing a non-Ready SubConn to be returned.
var wg sync.WaitGroup
wg.Add(1)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The loneliest waitgroup. Maybe use a channel instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

go func() {
for i := 0; i < 20; i++ {
tc.UnaryCall(ctx, &testpb.SimpleRequest{})
}
wg.Done()
}()
te.srv.Stop()
wg.Wait()
if len(b.pickOptions) != len(b.doneInfo) {
t.Fatalf("Got %d picks, %d doneInfo, want equal amount", len(b.pickOptions), len(b.doneInfo))
}
}