transport: remove RequireHandshakeHybrid support (#2529)

This removes RequireHandshakeHybrid support and changes the default behavior
to RequireHandshakeOn. Dial calls will now block and wait for a successful
handshake before proceeding. Users relying on the old hybrid behavior (cmux
users) should consult https://github.com/soheilhy/cmux/issues/64.

Also, several tests have been updated to take this into consideration by
sending settings frames.
This commit is contained in:
Jean de Klerk 2019-02-27 11:04:46 -07:00 committed by GitHub
parent a51d23e017
commit 5878d965b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 172 additions and 239 deletions

View File

@ -1019,7 +1019,19 @@ func (ac *addrConn) resetTransport() {
reconnect := grpcsync.NewEvent() reconnect := grpcsync.NewEvent()
prefaceReceived := make(chan struct{}) prefaceReceived := make(chan struct{})
newTr, err := ac.createTransport(addr, copts, connectDeadline, reconnect, prefaceReceived) newTr, err := ac.createTransport(addr, copts, connectDeadline, reconnect, prefaceReceived)
if err == nil { if err != nil {
ac.cc.blockingpicker.updateConnectionError(err)
hcancel()
if err == errConnClosing {
return
}
if tryNextAddrFromStart.HasFired() {
break addrLoop
}
continue
}
ac.mu.Lock() ac.mu.Lock()
ac.curAddr = addr ac.curAddr = addr
ac.transport = newTr ac.transport = newTr
@ -1047,57 +1059,10 @@ func (ac *addrConn) resetTransport() {
ac.updateConnectivityState(connectivity.Ready) ac.updateConnectivityState(connectivity.Ready)
ac.mu.Unlock() ac.mu.Unlock()
} }
} else {
hcancel()
if err == errConnClosing {
return
}
if tryNextAddrFromStart.HasFired() {
break addrLoop
}
continue
}
ac.mu.Lock()
reqHandshake := ac.dopts.reqHandshake
ac.mu.Unlock()
<-reconnect.Done() <-reconnect.Done()
hcancel() hcancel()
if reqHandshake == envconfig.RequireHandshakeHybrid {
// In RequireHandshakeHybrid mode, we must check to see whether
// server preface has arrived yet to decide whether to start
// reconnecting at the top of the list (server preface received)
// or continue with the next addr in the list as if the
// connection were not successful (server preface not received).
select {
case <-prefaceReceived:
// We received a server preface - huzzah! We consider this
// a success and restart from the top of the addr list.
ac.mu.Lock()
ac.backoffIdx = 0
ac.mu.Unlock()
break addrLoop
default:
// Despite having set state to READY, in hybrid mode we
// consider this a failure and continue connecting at the
// next addr in the list.
ac.mu.Lock()
if ac.state == connectivity.Shutdown {
ac.mu.Unlock()
return
}
ac.updateConnectivityState(connectivity.TransientFailure)
ac.mu.Unlock()
if tryNextAddrFromStart.HasFired() {
break addrLoop
}
}
} else {
// In RequireHandshakeOn mode, we would have already waited for // In RequireHandshakeOn mode, we would have already waited for
// the server preface, so we consider this a success and restart // the server preface, so we consider this a success and restart
// from the top of the addr list. In RequireHandshakeOff mode, // from the top of the addr list. In RequireHandshakeOff mode,
@ -1109,7 +1074,6 @@ func (ac *addrConn) resetTransport() {
ac.mu.Unlock() ac.mu.Unlock()
break addrLoop break addrLoop
} }
}
// After exhausting all addresses, or after need to reconnect after a // After exhausting all addresses, or after need to reconnect after a
// READY, the addrConn enters TRANSIENT_FAILURE. // READY, the addrConn enters TRANSIENT_FAILURE.
@ -1154,8 +1118,6 @@ func (ac *addrConn) createTransport(addr resolver.Address, copts transport.Conne
Authority: ac.cc.authority, Authority: ac.cc.authority,
} }
prefaceTimer := time.NewTimer(time.Until(connectDeadline))
onGoAway := func(r transport.GoAwayReason) { onGoAway := func(r transport.GoAwayReason) {
ac.mu.Lock() ac.mu.Lock()
ac.adjustParams(r) ac.adjustParams(r)
@ -1165,13 +1127,11 @@ func (ac *addrConn) createTransport(addr resolver.Address, copts transport.Conne
onClose := func() { onClose := func() {
close(onCloseCalled) close(onCloseCalled)
prefaceTimer.Stop()
reconnect.Fire() reconnect.Fire()
} }
onPrefaceReceipt := func() { onPrefaceReceipt := func() {
close(prefaceReceived) close(prefaceReceived)
prefaceTimer.Stop()
} }
connectCtx, cancel := context.WithDeadline(ac.ctx, connectDeadline) connectCtx, cancel := context.WithDeadline(ac.ctx, connectDeadline)
@ -1181,38 +1141,8 @@ func (ac *addrConn) createTransport(addr resolver.Address, copts transport.Conne
} }
newTr, err := transport.NewClientTransport(connectCtx, ac.cc.ctx, target, copts, onPrefaceReceipt, onGoAway, onClose) newTr, err := transport.NewClientTransport(connectCtx, ac.cc.ctx, target, copts, onPrefaceReceipt, onGoAway, onClose)
if err == nil {
if ac.dopts.reqHandshake == envconfig.RequireHandshakeOn {
select {
case <-prefaceTimer.C:
// We didn't get the preface in time.
newTr.Close()
err = errors.New("timed out waiting for server handshake")
case <-prefaceReceived:
// We got the preface - huzzah! things are good.
case <-onCloseCalled:
// The transport has already closed - noop.
return nil, errors.New("connection closed")
}
} else if ac.dopts.reqHandshake == envconfig.RequireHandshakeHybrid {
go func() {
select {
case <-prefaceTimer.C:
// We didn't get the preface in time.
newTr.Close()
case <-prefaceReceived:
// We got the preface just in the nick of time - huzzah!
case <-onCloseCalled:
// The transport has already closed - noop.
}
}()
}
}
if err != nil { if err != nil {
// newTr is either nil, or closed. // newTr is either nil, or closed.
ac.cc.blockingpicker.updateConnectionError(err)
ac.mu.Lock() ac.mu.Lock()
if ac.state == connectivity.Shutdown { if ac.state == connectivity.Shutdown {
// ac.tearDown(...) has been invoked. // ac.tearDown(...) has been invoked.
@ -1225,6 +1155,22 @@ func (ac *addrConn) createTransport(addr resolver.Address, copts transport.Conne
return nil, err return nil, err
} }
if ac.dopts.reqHandshake == envconfig.RequireHandshakeOn {
select {
case <-time.After(connectDeadline.Sub(time.Now())):
// We didn't get the preface in time.
newTr.Close()
grpclog.Warningf("grpc: addrConn.createTransport failed to connect to %v: didn't receive server preface in time. Reconnecting...", addr)
return nil, errors.New("timed out waiting for server handshake")
case <-prefaceReceived:
// We got the preface - huzzah! things are good.
case <-onCloseCalled:
// The transport has already closed - noop.
return nil, errors.New("connection closed")
// TODO(deklerk) this should bail on ac.ctx.Done(). Add a test and fix.
}
}
// Now there is a viable transport to be use, so set ac.transport to reflect the new viable transport. // Now there is a viable transport to be use, so set ac.transport to reflect the new viable transport.
ac.mu.Lock() ac.mu.Lock()
if ac.state == connectivity.Shutdown { if ac.state == connectivity.Shutdown {

View File

@ -121,15 +121,6 @@ func (s) TestDialWithMultipleBackendsNotSendingServerPreface(t *testing.T) {
var allReqHSSettings = []envconfig.RequireHandshakeSetting{ var allReqHSSettings = []envconfig.RequireHandshakeSetting{
envconfig.RequireHandshakeOff, envconfig.RequireHandshakeOff,
envconfig.RequireHandshakeOn, envconfig.RequireHandshakeOn,
envconfig.RequireHandshakeHybrid,
}
var reqNoHSSettings = []envconfig.RequireHandshakeSetting{
envconfig.RequireHandshakeOff,
envconfig.RequireHandshakeHybrid,
}
var reqHSBeforeSuccess = []envconfig.RequireHandshakeSetting{
envconfig.RequireHandshakeOn,
envconfig.RequireHandshakeHybrid,
} }
func (s) TestDialWaitsForServerSettings(t *testing.T) { func (s) TestDialWaitsForServerSettings(t *testing.T) {
@ -332,10 +323,8 @@ func (s) TestDialDoesNotWaitForServerSettings(t *testing.T) {
// Restore current setting after test. // Restore current setting after test.
old := envconfig.RequireHandshake old := envconfig.RequireHandshake
defer func() { envconfig.RequireHandshake = old }() defer func() { envconfig.RequireHandshake = old }()
envconfig.RequireHandshake = envconfig.RequireHandshakeOff
// Test with "off" and "hybrid".
for _, setting := range reqNoHSSettings {
envconfig.RequireHandshake = setting
lis, err := net.Listen("tcp", "localhost:0") lis, err := net.Listen("tcp", "localhost:0")
if err != nil { if err != nil {
t.Fatalf("Error while listening. Err: %v", err) t.Fatalf("Error while listening. Err: %v", err)
@ -358,7 +347,6 @@ func (s) TestDialDoesNotWaitForServerSettings(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() defer cancel()
client, err := DialContext(ctx, lis.Addr().String(), WithInsecure(), WithBlock()) client, err := DialContext(ctx, lis.Addr().String(), WithInsecure(), WithBlock())
if err != nil { if err != nil {
t.Fatalf("DialContext returned err =%v; want nil", err) t.Fatalf("DialContext returned err =%v; want nil", err)
} }
@ -368,14 +356,13 @@ func (s) TestDialDoesNotWaitForServerSettings(t *testing.T) {
t.Fatalf("client.GetState() = %v; want connectivity.Ready", state) t.Fatalf("client.GetState() = %v; want connectivity.Ready", state)
} }
close(dialDone) close(dialDone)
<-done
}
} }
func (s) TestCloseConnectionWhenServerPrefaceNotReceived(t *testing.T) { func (s) TestCloseConnectionWhenServerPrefaceNotReceived(t *testing.T) {
// Restore current setting after test. // Restore current setting after test.
old := envconfig.RequireHandshake old := envconfig.RequireHandshake
defer func() { envconfig.RequireHandshake = old }() defer func() { envconfig.RequireHandshake = old }()
envconfig.RequireHandshake = envconfig.RequireHandshakeOn
// 1. Client connects to a server that doesn't send preface. // 1. Client connects to a server that doesn't send preface.
// 2. After minConnectTimeout(500 ms here), client disconnects and retries. // 2. After minConnectTimeout(500 ms here), client disconnects and retries.
@ -384,10 +371,6 @@ func (s) TestCloseConnectionWhenServerPrefaceNotReceived(t *testing.T) {
cleanup := setMinConnectTimeout(time.Millisecond * 500) cleanup := setMinConnectTimeout(time.Millisecond * 500)
defer cleanup() defer cleanup()
// Test with "on" and "hybrid".
for _, setting := range reqHSBeforeSuccess {
envconfig.RequireHandshake = setting
lis, err := net.Listen("tcp", "localhost:0") lis, err := net.Listen("tcp", "localhost:0")
if err != nil { if err != nil {
t.Fatalf("Error while listening. Err: %v", err) t.Fatalf("Error while listening. Err: %v", err)
@ -458,7 +441,6 @@ func (s) TestCloseConnectionWhenServerPrefaceNotReceived(t *testing.T) {
client.Close() client.Close()
<-done <-done
} }
}
func (s) TestBackoffWhenNoServerPrefaceReceived(t *testing.T) { func (s) TestBackoffWhenNoServerPrefaceReceived(t *testing.T) {
lis, err := net.Listen("tcp", "localhost:0") lis, err := net.Listen("tcp", "localhost:0")

View File

@ -34,13 +34,9 @@ const (
type RequireHandshakeSetting int type RequireHandshakeSetting int
const ( const (
// RequireHandshakeHybrid (default, deprecated) indicates to not wait for // RequireHandshakeOn indicates to wait for handshake before considering a
// handshake before considering a connection ready, but wait before // connection ready/successful.
// considering successful. RequireHandshakeOn RequireHandshakeSetting = iota
RequireHandshakeHybrid RequireHandshakeSetting = iota
// RequireHandshakeOn (default after the 1.17 release) indicates to wait
// for handshake before considering a connection ready/successful.
RequireHandshakeOn
// RequireHandshakeOff indicates to not wait for handshake before // RequireHandshakeOff indicates to not wait for handshake before
// considering a connection ready/successful. // considering a connection ready/successful.
RequireHandshakeOff RequireHandshakeOff
@ -53,7 +49,7 @@ var (
// environment variable. // environment variable.
// //
// Will be removed after the 1.18 release. // Will be removed after the 1.18 release.
RequireHandshake RequireHandshakeSetting RequireHandshake = RequireHandshakeOn
) )
func init() { func init() {
@ -64,8 +60,5 @@ func init() {
RequireHandshake = RequireHandshakeOn RequireHandshake = RequireHandshakeOn
case "off": case "off":
RequireHandshake = RequireHandshakeOff RequireHandshake = RequireHandshakeOff
case "hybrid":
// Will be removed after the 1.17 release.
RequireHandshake = RequireHandshakeHybrid
} }
} }

View File

@ -27,8 +27,8 @@ import (
"testing" "testing"
"time" "time"
"golang.org/x/net/http2"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/internal/envconfig" "google.golang.org/grpc/internal/envconfig"
testpb "google.golang.org/grpc/test/grpc_testing" testpb "google.golang.org/grpc/test/grpc_testing"
) )
@ -52,6 +52,13 @@ func (d *delayListener) Accept() (net.Conn, error) {
default: default:
close(d.acceptCalled) close(d.acceptCalled)
conn, err := d.Listener.Accept() conn, err := d.Listener.Accept()
if err != nil {
return nil, err
}
framer := http2.NewFramer(conn, conn)
if err = framer.WriteSettings(http2.Setting{}); err != nil {
return nil, err
}
// Allow closing of listener only after accept. // Allow closing of listener only after accept.
// Note: Dial can return successfully, yet Accept // Note: Dial can return successfully, yet Accept
// might now have finished. // might now have finished.
@ -107,10 +114,14 @@ func (d *delayConn) Read(b []byte) (n int, err error) {
} }
func (s) TestGracefulStop(t *testing.T) { func (s) TestGracefulStop(t *testing.T) {
// Set default behavior and restore current setting after test. // We need to turn off RequireHandshake because if it were on, it would
// block forever waiting to read the handshake, and the delayConn would
// never let it (the delay is intended to block until later in the test).
//
// Restore current setting after test.
old := envconfig.RequireHandshake old := envconfig.RequireHandshake
envconfig.RequireHandshake = envconfig.RequireHandshakeOff
defer func() { envconfig.RequireHandshake = old }() defer func() { envconfig.RequireHandshake = old }()
envconfig.RequireHandshake = envconfig.RequireHandshakeOff
// This test ensures GracefulStop cannot race and break RPCs on new // This test ensures GracefulStop cannot race and break RPCs on new
// connections created after GracefulStop was called but before // connections created after GracefulStop was called but before

1
vet.sh
View File

@ -119,5 +119,6 @@ google.golang.org/grpc/stats/stats_test.go:SA1019
google.golang.org/grpc/test/channelz_test.go:SA1019 google.golang.org/grpc/test/channelz_test.go:SA1019
google.golang.org/grpc/test/end2end_test.go:SA1019 google.golang.org/grpc/test/end2end_test.go:SA1019
google.golang.org/grpc/test/healthcheck_test.go:SA1019 google.golang.org/grpc/test/healthcheck_test.go:SA1019
google.golang.org/grpc/clientconn.go:S1024
' ./... ' ./...
misspell -error . misspell -error .