|
|
@@ -114,14 +114,42 @@ func dial(fn func(context.Context, string) (net.Conn, error), ctx context.Contex
|
|
|
return dialContext(ctx, "tcp", addr)
|
|
|
}
|
|
|
|
|
|
+func isTemporary(err error) bool {
|
|
|
+ switch err {
|
|
|
+ case io.EOF:
|
|
|
+ // Connection closures may be resolved upon retry, and are thus
|
|
|
+ // treated as temporary.
|
|
|
+ return true
|
|
|
+ case context.DeadlineExceeded:
|
|
|
+ // In Go 1.7, context.DeadlineExceeded implements Timeout(), and this
|
|
|
+ // special case is not needed. Until then, we need to keep this
|
|
|
+ // clause.
|
|
|
+ return true
|
|
|
+ }
|
|
|
+
|
|
|
+ switch err := err.(type) {
|
|
|
+ case interface {
|
|
|
+ Temporary() bool
|
|
|
+ }:
|
|
|
+ return err.Temporary()
|
|
|
+ case interface {
|
|
|
+ Timeout() bool
|
|
|
+ }:
|
|
|
+ // Timeouts may be resolved upon retry, and are thus treated as
|
|
|
+ // temporary.
|
|
|
+ return err.Timeout()
|
|
|
+ }
|
|
|
+ return false
|
|
|
+}
|
|
|
+
|
|
|
// newHTTP2Client constructs a connected ClientTransport to addr based on HTTP2
|
|
|
// and starts to receive messages on it. Non-nil error returns if construction
|
|
|
// fails.
|
|
|
func newHTTP2Client(ctx context.Context, addr string, opts ConnectOptions) (_ ClientTransport, err error) {
|
|
|
scheme := "http"
|
|
|
- conn, connErr := dial(opts.Dialer, ctx, addr)
|
|
|
- if connErr != nil {
|
|
|
- return nil, ConnectionErrorf(true, connErr, "transport: %v", connErr)
|
|
|
+ conn, err := dial(opts.Dialer, ctx, addr)
|
|
|
+ if err != nil {
|
|
|
+ return nil, connectionErrorf(true, err, "transport: %v", err)
|
|
|
}
|
|
|
// Any further errors will close the underlying connection
|
|
|
defer func(conn net.Conn) {
|
|
|
@@ -132,12 +160,13 @@ func newHTTP2Client(ctx context.Context, addr string, opts ConnectOptions) (_ Cl
|
|
|
var authInfo credentials.AuthInfo
|
|
|
if creds := opts.TransportCredentials; creds != nil {
|
|
|
scheme = "https"
|
|
|
- conn, authInfo, connErr = creds.ClientHandshake(ctx, addr, conn)
|
|
|
- }
|
|
|
- if connErr != nil {
|
|
|
- // Credentials handshake error is not a temporary error (unless the error
|
|
|
- // was the connection closing).
|
|
|
- return nil, ConnectionErrorf(connErr == io.EOF, connErr, "transport: %v", connErr)
|
|
|
+ conn, authInfo, err = creds.ClientHandshake(ctx, addr, conn)
|
|
|
+ if err != nil {
|
|
|
+ // Credentials handshake errors are typically considered permanent
|
|
|
+ // to avoid retrying on e.g. bad certificates.
|
|
|
+ temp := isTemporary(err)
|
|
|
+ return nil, connectionErrorf(temp, err, "transport: %v", err)
|
|
|
+ }
|
|
|
}
|
|
|
ua := primaryUA
|
|
|
if opts.UserAgent != "" {
|
|
|
@@ -176,11 +205,11 @@ func newHTTP2Client(ctx context.Context, addr string, opts ConnectOptions) (_ Cl
|
|
|
n, err := t.conn.Write(clientPreface)
|
|
|
if err != nil {
|
|
|
t.Close()
|
|
|
- return nil, ConnectionErrorf(true, err, "transport: %v", err)
|
|
|
+ return nil, connectionErrorf(true, err, "transport: %v", err)
|
|
|
}
|
|
|
if n != len(clientPreface) {
|
|
|
t.Close()
|
|
|
- return nil, ConnectionErrorf(true, err, "transport: preface mismatch, wrote %d bytes; want %d", n, len(clientPreface))
|
|
|
+ return nil, connectionErrorf(true, err, "transport: preface mismatch, wrote %d bytes; want %d", n, len(clientPreface))
|
|
|
}
|
|
|
if initialWindowSize != defaultWindowSize {
|
|
|
err = t.framer.writeSettings(true, http2.Setting{
|
|
|
@@ -192,13 +221,13 @@ func newHTTP2Client(ctx context.Context, addr string, opts ConnectOptions) (_ Cl
|
|
|
}
|
|
|
if err != nil {
|
|
|
t.Close()
|
|
|
- return nil, ConnectionErrorf(true, err, "transport: %v", err)
|
|
|
+ return nil, connectionErrorf(true, err, "transport: %v", err)
|
|
|
}
|
|
|
// Adjust the connection flow control window if needed.
|
|
|
if delta := uint32(initialConnWindowSize - defaultWindowSize); delta > 0 {
|
|
|
if err := t.framer.writeWindowUpdate(true, 0, delta); err != nil {
|
|
|
t.Close()
|
|
|
- return nil, ConnectionErrorf(true, err, "transport: %v", err)
|
|
|
+ return nil, connectionErrorf(true, err, "transport: %v", err)
|
|
|
}
|
|
|
}
|
|
|
go t.controller()
|
|
|
@@ -223,8 +252,10 @@ func (t *http2Client) newStream(ctx context.Context, callHdr *CallHdr) *Stream {
|
|
|
s.windowHandler = func(n int) {
|
|
|
t.updateWindow(s, uint32(n))
|
|
|
}
|
|
|
- // Make a stream be able to cancel the pending operations by itself.
|
|
|
- s.ctx, s.cancel = context.WithCancel(ctx)
|
|
|
+ // The client side stream context should have exactly the same life cycle with the user provided context.
|
|
|
+ // That means, s.ctx should be read-only. And s.ctx is done iff ctx is done.
|
|
|
+ // So we use the original context here instead of creating a copy.
|
|
|
+ s.ctx = ctx
|
|
|
s.dec = &recvBufferReader{
|
|
|
ctx: s.ctx,
|
|
|
goAway: s.goAway,
|
|
|
@@ -236,16 +267,6 @@ func (t *http2Client) newStream(ctx context.Context, callHdr *CallHdr) *Stream {
|
|
|
// NewStream creates a stream and register it into the transport as "active"
|
|
|
// streams.
|
|
|
func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (_ *Stream, err error) {
|
|
|
- // Record the timeout value on the context.
|
|
|
- var timeout time.Duration
|
|
|
- if dl, ok := ctx.Deadline(); ok {
|
|
|
- timeout = dl.Sub(time.Now())
|
|
|
- }
|
|
|
- select {
|
|
|
- case <-ctx.Done():
|
|
|
- return nil, ContextErr(ctx.Err())
|
|
|
- default:
|
|
|
- }
|
|
|
pr := &peer.Peer{
|
|
|
Addr: t.conn.RemoteAddr(),
|
|
|
}
|
|
|
@@ -266,12 +287,12 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (_ *Strea
|
|
|
}
|
|
|
pos := strings.LastIndex(callHdr.Method, "/")
|
|
|
if pos == -1 {
|
|
|
- return nil, StreamErrorf(codes.InvalidArgument, "transport: malformed method name: %q", callHdr.Method)
|
|
|
+ return nil, streamErrorf(codes.InvalidArgument, "transport: malformed method name: %q", callHdr.Method)
|
|
|
}
|
|
|
audience := "https://" + callHdr.Host + port + callHdr.Method[:pos]
|
|
|
data, err := c.GetRequestMetadata(ctx, audience)
|
|
|
if err != nil {
|
|
|
- return nil, StreamErrorf(codes.InvalidArgument, "transport: %v", err)
|
|
|
+ return nil, streamErrorf(codes.InvalidArgument, "transport: %v", err)
|
|
|
}
|
|
|
for k, v := range data {
|
|
|
authData[k] = v
|
|
|
@@ -352,9 +373,12 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (_ *Strea
|
|
|
if callHdr.SendCompress != "" {
|
|
|
t.hEnc.WriteField(hpack.HeaderField{Name: "grpc-encoding", Value: callHdr.SendCompress})
|
|
|
}
|
|
|
- if timeout > 0 {
|
|
|
+ if dl, ok := ctx.Deadline(); ok {
|
|
|
+ // Send out timeout regardless its value. The server can detect timeout context by itself.
|
|
|
+ timeout := dl.Sub(time.Now())
|
|
|
t.hEnc.WriteField(hpack.HeaderField{Name: "grpc-timeout", Value: encodeTimeout(timeout)})
|
|
|
}
|
|
|
+
|
|
|
for k, v := range authData {
|
|
|
// Capital header names are illegal in HTTP/2.
|
|
|
k = strings.ToLower(k)
|
|
|
@@ -408,7 +432,7 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (_ *Strea
|
|
|
}
|
|
|
if err != nil {
|
|
|
t.notifyError(err)
|
|
|
- return nil, ConnectionErrorf(true, err, "transport: %v", err)
|
|
|
+ return nil, connectionErrorf(true, err, "transport: %v", err)
|
|
|
}
|
|
|
}
|
|
|
t.writableChan <- 0
|
|
|
@@ -454,7 +478,7 @@ func (t *http2Client) CloseStream(s *Stream, err error) {
|
|
|
}
|
|
|
s.state = streamDone
|
|
|
s.mu.Unlock()
|
|
|
- if _, ok := err.(StreamError); ok {
|
|
|
+ if se, ok := err.(StreamError); ok && se.Code != codes.DeadlineExceeded {
|
|
|
t.controlBuf.put(&resetStream{s.id, http2.ErrCodeCancel})
|
|
|
}
|
|
|
}
|
|
|
@@ -622,7 +646,7 @@ func (t *http2Client) Write(s *Stream, data []byte, opts *Options) error {
|
|
|
// invoked.
|
|
|
if err := t.framer.writeData(forceFlush, s.id, endStream, p); err != nil {
|
|
|
t.notifyError(err)
|
|
|
- return ConnectionErrorf(true, err, "transport: %v", err)
|
|
|
+ return connectionErrorf(true, err, "transport: %v", err)
|
|
|
}
|
|
|
if t.framer.adjustNumWriters(-1) == 0 {
|
|
|
t.framer.flushWrite()
|
|
|
@@ -670,7 +694,7 @@ func (t *http2Client) updateWindow(s *Stream, n uint32) {
|
|
|
func (t *http2Client) handleData(f *http2.DataFrame) {
|
|
|
size := len(f.Data())
|
|
|
if err := t.fc.onData(uint32(size)); err != nil {
|
|
|
- t.notifyError(ConnectionErrorf(true, err, "%v", err))
|
|
|
+ t.notifyError(connectionErrorf(true, err, "%v", err))
|
|
|
return
|
|
|
}
|
|
|
// Select the right stream to dispatch.
|
|
|
@@ -776,7 +800,7 @@ func (t *http2Client) handleGoAway(f *http2.GoAwayFrame) {
|
|
|
if t.state == reachable || t.state == draining {
|
|
|
if f.LastStreamID > 0 && f.LastStreamID%2 != 1 {
|
|
|
t.mu.Unlock()
|
|
|
- t.notifyError(ConnectionErrorf(true, nil, "received illegal http2 GOAWAY frame: stream ID %d is even", f.LastStreamID))
|
|
|
+ t.notifyError(connectionErrorf(true, nil, "received illegal http2 GOAWAY frame: stream ID %d is even", f.LastStreamID))
|
|
|
return
|
|
|
}
|
|
|
select {
|
|
|
@@ -785,7 +809,7 @@ func (t *http2Client) handleGoAway(f *http2.GoAwayFrame) {
|
|
|
// t.goAway has been closed (i.e.,multiple GoAways).
|
|
|
if id < f.LastStreamID {
|
|
|
t.mu.Unlock()
|
|
|
- t.notifyError(ConnectionErrorf(true, nil, "received illegal http2 GOAWAY frame: previously recv GOAWAY frame with LastStramID %d, currently recv %d", id, f.LastStreamID))
|
|
|
+ t.notifyError(connectionErrorf(true, nil, "received illegal http2 GOAWAY frame: previously recv GOAWAY frame with LastStramID %d, currently recv %d", id, f.LastStreamID))
|
|
|
return
|
|
|
}
|
|
|
t.prevGoAwayID = id
|
|
|
@@ -823,6 +847,12 @@ func (t *http2Client) operateHeaders(frame *http2.MetaHeadersFrame) {
|
|
|
state.processHeaderField(hf)
|
|
|
}
|
|
|
if state.err != nil {
|
|
|
+ s.mu.Lock()
|
|
|
+ if !s.headerDone {
|
|
|
+ close(s.headerChan)
|
|
|
+ s.headerDone = true
|
|
|
+ }
|
|
|
+ s.mu.Unlock()
|
|
|
s.write(recvMsg{err: state.err})
|
|
|
// Something wrong. Stops reading even when there is remaining.
|
|
|
return
|
|
|
@@ -900,7 +930,7 @@ func (t *http2Client) reader() {
|
|
|
t.mu.Unlock()
|
|
|
if s != nil {
|
|
|
// use error detail to provide better err message
|
|
|
- handleMalformedHTTP2(s, StreamErrorf(http2ErrConvTab[se.Code], "%v", t.framer.errorDetail()))
|
|
|
+ handleMalformedHTTP2(s, streamErrorf(http2ErrConvTab[se.Code], "%v", t.framer.errorDetail()))
|
|
|
}
|
|
|
continue
|
|
|
} else {
|