Skip to content

Commit d534699

Browse files
authored
transport: fix transparent retries when per-RPC credentials are in use (#4785)
1 parent 5417cf8 commit d534699

File tree

2 files changed

+20
-24
lines changed

2 files changed

+20
-24
lines changed

internal/transport/http2_client.go

+19-23
Original file line numberDiff line numberDiff line change
@@ -616,12 +616,22 @@ func (t *http2Client) getCallAuthData(ctx context.Context, audience string, call
616616
return callAuthData, nil
617617
}
618618

619-
// NewStreamError wraps an error and reports additional information.
619+
// NewStreamError wraps an error and reports additional information. Typically
620+
// NewStream errors result in transparent retry, as they mean nothing went onto
621+
// the wire. However, there are two notable exceptions:
622+
//
623+
// 1. If the stream headers violate the max header list size allowed by the
624+
// server. In this case there is no reason to retry at all, as it is
625+
// assumed the RPC would continue to fail on subsequent attempts.
626+
// 2. If the credentials errored when requesting their headers. In this case,
627+
// it's possible a retry can fix the problem, but indefinitely transparently
628+
// retrying is not appropriate as it is likely the credentials, if they can
629+
// eventually succeed, would need I/O to do so.
620630
type NewStreamError struct {
621631
Err error
622632

623-
DoNotRetry bool
624-
PerformedIO bool
633+
DoNotRetry bool
634+
DoNotTransparentRetry bool
625635
}
626636

627637
func (e NewStreamError) Error() string {
@@ -631,24 +641,10 @@ func (e NewStreamError) Error() string {
631641
// NewStream creates a stream and registers it into the transport as "active"
632642
// streams. All non-nil errors returned will be *NewStreamError.
633643
func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (_ *Stream, err error) {
634-
defer func() {
635-
if err != nil {
636-
nse, ok := err.(*NewStreamError)
637-
if !ok {
638-
nse = &NewStreamError{Err: err}
639-
}
640-
if len(t.perRPCCreds) > 0 || callHdr.Creds != nil {
641-
// We may have performed I/O in the per-RPC creds callback, so do not
642-
// allow transparent retry.
643-
nse.PerformedIO = true
644-
}
645-
err = nse
646-
}
647-
}()
648644
ctx = peer.NewContext(ctx, t.getPeer())
649645
headerFields, err := t.createHeaderFields(ctx, callHdr)
650646
if err != nil {
651-
return nil, err
647+
return nil, &NewStreamError{Err: err, DoNotTransparentRetry: true}
652648
}
653649
s := t.newStream(ctx, callHdr)
654650
cleanup := func(err error) {
@@ -748,7 +744,7 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (_ *Strea
748744
return true
749745
}, hdr)
750746
if err != nil {
751-
return nil, err
747+
return nil, &NewStreamError{Err: err}
752748
}
753749
if success {
754750
break
@@ -759,12 +755,12 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (_ *Strea
759755
firstTry = false
760756
select {
761757
case <-ch:
762-
case <-s.ctx.Done():
763-
return nil, ContextErr(s.ctx.Err())
758+
case <-ctx.Done():
759+
return nil, &NewStreamError{Err: ContextErr(ctx.Err())}
764760
case <-t.goAway:
765-
return nil, errStreamDrain
761+
return nil, &NewStreamError{Err: errStreamDrain}
766762
case <-t.ctx.Done():
767-
return nil, ErrConnClosing
763+
return nil, &NewStreamError{Err: ErrConnClosing}
768764
}
769765
}
770766
if t.statsHandler != nil {

stream.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -547,7 +547,7 @@ func (cs *clientStream) shouldRetry(err error) (bool, error) {
547547
// In the event of a non-IO operation error from NewStream, we never
548548
// attempted to write anything to the wire, so we can retry
549549
// indefinitely.
550-
if !nse.PerformedIO {
550+
if !nse.DoNotTransparentRetry {
551551
return true, nil
552552
}
553553
}

0 commit comments

Comments
 (0)