vendor: Add github.com/grpc-ecosystem/go-grpc-middleware/retry
This commit is contained in:
parent
e2450bb251
commit
55ed831c56
|
@ -0,0 +1,201 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
44
vendor/github.com/grpc-ecosystem/go-grpc-middleware/retry/backoff.go
generated
vendored
Normal file
44
vendor/github.com/grpc-ecosystem/go-grpc-middleware/retry/backoff.go
generated
vendored
Normal file
|
@ -0,0 +1,44 @@
|
|||
// Copyright 2016 Michal Witkowski. All Rights Reserved.
|
||||
// See LICENSE for licensing terms.
|
||||
|
||||
package grpc_retry
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/grpc-ecosystem/go-grpc-middleware/util/backoffutils"
|
||||
)
|
||||
|
||||
// BackoffLinear is very simple: it waits for a fixed period of time between calls.
|
||||
func BackoffLinear(waitBetween time.Duration) BackoffFunc {
|
||||
return func(attempt uint) time.Duration {
|
||||
return waitBetween
|
||||
}
|
||||
}
|
||||
|
||||
// BackoffLinearWithJitter waits a set period of time, allowing for jitter (fractional adjustment).
|
||||
//
|
||||
// For example waitBetween=1s and jitter=0.10 can generate waits between 900ms and 1100ms.
|
||||
func BackoffLinearWithJitter(waitBetween time.Duration, jitterFraction float64) BackoffFunc {
|
||||
return func(attempt uint) time.Duration {
|
||||
return backoffutils.JitterUp(waitBetween, jitterFraction)
|
||||
}
|
||||
}
|
||||
|
||||
// BackoffExponential produces increasing intervals for each attempt.
|
||||
//
|
||||
// The scalar is multiplied times 2 raised to the current attempt. So the first
|
||||
// retry with a scalar of 100ms is 100ms, while the 5th attempt would be 1.6s.
|
||||
func BackoffExponential(scalar time.Duration) BackoffFunc {
|
||||
return func(attempt uint) time.Duration {
|
||||
return scalar * time.Duration(backoffutils.ExponentBase2(attempt))
|
||||
}
|
||||
}
|
||||
|
||||
// BackoffExponentialWithJitter creates an exponential backoff like
|
||||
// BackoffExponential does, but adds jitter.
|
||||
func BackoffExponentialWithJitter(scalar time.Duration, jitterFraction float64) BackoffFunc {
|
||||
return func(attempt uint) time.Duration {
|
||||
return backoffutils.JitterUp(scalar*time.Duration(backoffutils.ExponentBase2(attempt)), jitterFraction)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright 2016 Michal Witkowski. All Rights Reserved.
|
||||
// See LICENSE for licensing terms.
|
||||
|
||||
/*
|
||||
`grpc_retry` provides client-side request retry logic for gRPC.
|
||||
|
||||
Client-Side Request Retry Interceptor
|
||||
|
||||
It allows for automatic retry, inside the generated gRPC code of requests based on the gRPC status
|
||||
of the reply. It supports unary (1:1), and server stream (1:n) requests.
|
||||
|
||||
By default the interceptors *are disabled*, preventing accidental use of retries. You can easily
|
||||
override the number of retries (setting them to more than 0) with a `grpc.ClientOption`, e.g.:
|
||||
|
||||
myclient.Ping(ctx, goodPing, grpc_retry.WithMax(5))
|
||||
|
||||
Other default options are: retry on `ResourceExhausted` and `Unavailable` gRPC codes, use a 50ms
|
||||
linear backoff with 10% jitter.
|
||||
|
||||
For chained interceptors, the retry interceptor will call every interceptor that follows it
|
||||
whenever when a retry happens.
|
||||
|
||||
Please see examples for more advanced use.
|
||||
*/
|
||||
package grpc_retry
|
142
vendor/github.com/grpc-ecosystem/go-grpc-middleware/retry/options.go
generated
vendored
Normal file
142
vendor/github.com/grpc-ecosystem/go-grpc-middleware/retry/options.go
generated
vendored
Normal file
|
@ -0,0 +1,142 @@
|
|||
// Copyright 2016 Michal Witkowski. All Rights Reserved.
|
||||
// See LICENSE for licensing terms.
|
||||
|
||||
package grpc_retry
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultRetriableCodes is a set of well known types gRPC codes that should be retri-able.
|
||||
//
|
||||
// `ResourceExhausted` means that the user quota, e.g. per-RPC limits, have been reached.
|
||||
// `Unavailable` means that system is currently unavailable and the client should retry again.
|
||||
DefaultRetriableCodes = []codes.Code{codes.ResourceExhausted, codes.Unavailable}
|
||||
|
||||
defaultOptions = &options{
|
||||
max: 0, // disabled
|
||||
perCallTimeout: 0, // disabled
|
||||
includeHeader: true,
|
||||
codes: DefaultRetriableCodes,
|
||||
backoffFunc: BackoffFuncContext(func(ctx context.Context, attempt uint) time.Duration {
|
||||
return BackoffLinearWithJitter(50*time.Millisecond /*jitter*/, 0.10)(attempt)
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
// BackoffFunc denotes a family of functions that control the backoff duration between call retries.
|
||||
//
|
||||
// They are called with an identifier of the attempt, and should return a time the system client should
|
||||
// hold off for. If the time returned is longer than the `context.Context.Deadline` of the request
|
||||
// the deadline of the request takes precedence and the wait will be interrupted before proceeding
|
||||
// with the next iteration.
|
||||
type BackoffFunc func(attempt uint) time.Duration
|
||||
|
||||
// BackoffFuncContext denotes a family of functions that control the backoff duration between call retries.
|
||||
//
|
||||
// They are called with an identifier of the attempt, and should return a time the system client should
|
||||
// hold off for. If the time returned is longer than the `context.Context.Deadline` of the request
|
||||
// the deadline of the request takes precedence and the wait will be interrupted before proceeding
|
||||
// with the next iteration. The context can be used to extract request scoped metadata and context values.
|
||||
type BackoffFuncContext func(ctx context.Context, attempt uint) time.Duration
|
||||
|
||||
// Disable disables the retry behaviour on this call, or this interceptor.
|
||||
//
|
||||
// Its semantically the same to `WithMax`
|
||||
func Disable() CallOption {
|
||||
return WithMax(0)
|
||||
}
|
||||
|
||||
// WithMax sets the maximum number of retries on this call, or this interceptor.
|
||||
func WithMax(maxRetries uint) CallOption {
|
||||
return CallOption{applyFunc: func(o *options) {
|
||||
o.max = maxRetries
|
||||
}}
|
||||
}
|
||||
|
||||
// WithBackoff sets the `BackoffFunc` used to control time between retries.
|
||||
func WithBackoff(bf BackoffFunc) CallOption {
|
||||
return CallOption{applyFunc: func(o *options) {
|
||||
o.backoffFunc = BackoffFuncContext(func(ctx context.Context, attempt uint) time.Duration {
|
||||
return bf(attempt)
|
||||
})
|
||||
}}
|
||||
}
|
||||
|
||||
// WithBackoffContext sets the `BackoffFuncContext` used to control time between retries.
|
||||
func WithBackoffContext(bf BackoffFuncContext) CallOption {
|
||||
return CallOption{applyFunc: func(o *options) {
|
||||
o.backoffFunc = bf
|
||||
}}
|
||||
}
|
||||
|
||||
// WithCodes sets which codes should be retried.
|
||||
//
|
||||
// Please *use with care*, as you may be retrying non-idempotent calls.
|
||||
//
|
||||
// You cannot automatically retry on Cancelled and Deadline, please use `WithPerRetryTimeout` for these.
|
||||
func WithCodes(retryCodes ...codes.Code) CallOption {
|
||||
return CallOption{applyFunc: func(o *options) {
|
||||
o.codes = retryCodes
|
||||
}}
|
||||
}
|
||||
|
||||
// WithPerRetryTimeout sets the RPC timeout per call (including initial call) on this call, or this interceptor.
|
||||
//
|
||||
// The context.Deadline of the call takes precedence and sets the maximum time the whole invocation
|
||||
// will take, but WithPerRetryTimeout can be used to limit the RPC time per each call.
|
||||
//
|
||||
// For example, with context.Deadline = now + 10s, and WithPerRetryTimeout(3 * time.Seconds), each
|
||||
// of the retry calls (including the initial one) will have a deadline of now + 3s.
|
||||
//
|
||||
// A value of 0 disables the timeout overrides completely and returns to each retry call using the
|
||||
// parent `context.Deadline`.
|
||||
//
|
||||
// Note that when this is enabled, any DeadlineExceeded errors that are propagated up will be retried.
|
||||
func WithPerRetryTimeout(timeout time.Duration) CallOption {
|
||||
return CallOption{applyFunc: func(o *options) {
|
||||
o.perCallTimeout = timeout
|
||||
}}
|
||||
}
|
||||
|
||||
type options struct {
|
||||
max uint
|
||||
perCallTimeout time.Duration
|
||||
includeHeader bool
|
||||
codes []codes.Code
|
||||
backoffFunc BackoffFuncContext
|
||||
}
|
||||
|
||||
// CallOption is a grpc.CallOption that is local to grpc_retry.
|
||||
type CallOption struct {
|
||||
grpc.EmptyCallOption // make sure we implement private after() and before() fields so we don't panic.
|
||||
applyFunc func(opt *options)
|
||||
}
|
||||
|
||||
func reuseOrNewWithCallOptions(opt *options, callOptions []CallOption) *options {
|
||||
if len(callOptions) == 0 {
|
||||
return opt
|
||||
}
|
||||
optCopy := &options{}
|
||||
*optCopy = *opt
|
||||
for _, f := range callOptions {
|
||||
f.applyFunc(optCopy)
|
||||
}
|
||||
return optCopy
|
||||
}
|
||||
|
||||
func filterCallOptions(callOptions []grpc.CallOption) (grpcOptions []grpc.CallOption, retryOptions []CallOption) {
|
||||
for _, opt := range callOptions {
|
||||
if co, ok := opt.(CallOption); ok {
|
||||
retryOptions = append(retryOptions, co)
|
||||
} else {
|
||||
grpcOptions = append(grpcOptions, opt)
|
||||
}
|
||||
}
|
||||
return grpcOptions, retryOptions
|
||||
}
|
323
vendor/github.com/grpc-ecosystem/go-grpc-middleware/retry/retry.go
generated
vendored
Normal file
323
vendor/github.com/grpc-ecosystem/go-grpc-middleware/retry/retry.go
generated
vendored
Normal file
|
@ -0,0 +1,323 @@
|
|||
// Copyright 2016 Michal Witkowski. All Rights Reserved.
|
||||
// See LICENSE for licensing terms.
|
||||
|
||||
package grpc_retry
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/grpc-ecosystem/go-grpc-middleware/util/metautils"
|
||||
"golang.org/x/net/trace"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
const (
|
||||
AttemptMetadataKey = "x-retry-attempty"
|
||||
)
|
||||
|
||||
// UnaryClientInterceptor returns a new retrying unary client interceptor.
|
||||
//
|
||||
// The default configuration of the interceptor is to not retry *at all*. This behaviour can be
|
||||
// changed through options (e.g. WithMax) on creation of the interceptor or on call (through grpc.CallOptions).
|
||||
func UnaryClientInterceptor(optFuncs ...CallOption) grpc.UnaryClientInterceptor {
|
||||
intOpts := reuseOrNewWithCallOptions(defaultOptions, optFuncs)
|
||||
return func(parentCtx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
|
||||
grpcOpts, retryOpts := filterCallOptions(opts)
|
||||
callOpts := reuseOrNewWithCallOptions(intOpts, retryOpts)
|
||||
// short circuit for simplicity, and avoiding allocations.
|
||||
if callOpts.max == 0 {
|
||||
return invoker(parentCtx, method, req, reply, cc, grpcOpts...)
|
||||
}
|
||||
var lastErr error
|
||||
for attempt := uint(0); attempt < callOpts.max; attempt++ {
|
||||
if err := waitRetryBackoff(attempt, parentCtx, callOpts); err != nil {
|
||||
return err
|
||||
}
|
||||
callCtx := perCallContext(parentCtx, callOpts, attempt)
|
||||
lastErr = invoker(callCtx, method, req, reply, cc, grpcOpts...)
|
||||
// TODO(mwitkow): Maybe dial and transport errors should be retriable?
|
||||
if lastErr == nil {
|
||||
return nil
|
||||
}
|
||||
logTrace(parentCtx, "grpc_retry attempt: %d, got err: %v", attempt, lastErr)
|
||||
if isContextError(lastErr) {
|
||||
if parentCtx.Err() != nil {
|
||||
logTrace(parentCtx, "grpc_retry attempt: %d, parent context error: %v", attempt, parentCtx.Err())
|
||||
// its the parent context deadline or cancellation.
|
||||
return lastErr
|
||||
} else if callOpts.perCallTimeout != 0 {
|
||||
// We have set a perCallTimeout in the retry middleware, which would result in a context error if
|
||||
// the deadline was exceeded, in which case try again.
|
||||
logTrace(parentCtx, "grpc_retry attempt: %d, context error from retry call", attempt)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if !isRetriable(lastErr, callOpts) {
|
||||
return lastErr
|
||||
}
|
||||
}
|
||||
return lastErr
|
||||
}
|
||||
}
|
||||
|
||||
// StreamClientInterceptor returns a new retrying stream client interceptor for server side streaming calls.
|
||||
//
|
||||
// The default configuration of the interceptor is to not retry *at all*. This behaviour can be
|
||||
// changed through options (e.g. WithMax) on creation of the interceptor or on call (through grpc.CallOptions).
|
||||
//
|
||||
// Retry logic is available *only for ServerStreams*, i.e. 1:n streams, as the internal logic needs
|
||||
// to buffer the messages sent by the client. If retry is enabled on any other streams (ClientStreams,
|
||||
// BidiStreams), the retry interceptor will fail the call.
|
||||
func StreamClientInterceptor(optFuncs ...CallOption) grpc.StreamClientInterceptor {
|
||||
intOpts := reuseOrNewWithCallOptions(defaultOptions, optFuncs)
|
||||
return func(parentCtx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
|
||||
grpcOpts, retryOpts := filterCallOptions(opts)
|
||||
callOpts := reuseOrNewWithCallOptions(intOpts, retryOpts)
|
||||
// short circuit for simplicity, and avoiding allocations.
|
||||
if callOpts.max == 0 {
|
||||
return streamer(parentCtx, desc, cc, method, grpcOpts...)
|
||||
}
|
||||
if desc.ClientStreams {
|
||||
return nil, status.Errorf(codes.Unimplemented, "grpc_retry: cannot retry on ClientStreams, set grpc_retry.Disable()")
|
||||
}
|
||||
|
||||
var lastErr error
|
||||
for attempt := uint(0); attempt < callOpts.max; attempt++ {
|
||||
if err := waitRetryBackoff(attempt, parentCtx, callOpts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
callCtx := perCallContext(parentCtx, callOpts, 0)
|
||||
|
||||
var newStreamer grpc.ClientStream
|
||||
newStreamer, lastErr = streamer(callCtx, desc, cc, method, grpcOpts...)
|
||||
if lastErr == nil {
|
||||
retryingStreamer := &serverStreamingRetryingStream{
|
||||
ClientStream: newStreamer,
|
||||
callOpts: callOpts,
|
||||
parentCtx: parentCtx,
|
||||
streamerCall: func(ctx context.Context) (grpc.ClientStream, error) {
|
||||
return streamer(ctx, desc, cc, method, grpcOpts...)
|
||||
},
|
||||
}
|
||||
return retryingStreamer, nil
|
||||
}
|
||||
|
||||
logTrace(parentCtx, "grpc_retry attempt: %d, got err: %v", attempt, lastErr)
|
||||
if isContextError(lastErr) {
|
||||
if parentCtx.Err() != nil {
|
||||
logTrace(parentCtx, "grpc_retry attempt: %d, parent context error: %v", attempt, parentCtx.Err())
|
||||
// its the parent context deadline or cancellation.
|
||||
return nil, lastErr
|
||||
} else if callOpts.perCallTimeout != 0 {
|
||||
// We have set a perCallTimeout in the retry middleware, which would result in a context error if
|
||||
// the deadline was exceeded, in which case try again.
|
||||
logTrace(parentCtx, "grpc_retry attempt: %d, context error from retry call", attempt)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if !isRetriable(lastErr, callOpts) {
|
||||
return nil, lastErr
|
||||
}
|
||||
}
|
||||
return nil, lastErr
|
||||
}
|
||||
}
|
||||
|
||||
// type serverStreamingRetryingStream is the implementation of grpc.ClientStream that acts as a
|
||||
// proxy to the underlying call. If any of the RecvMsg() calls fail, it will try to reestablish
|
||||
// a new ClientStream according to the retry policy.
|
||||
type serverStreamingRetryingStream struct {
|
||||
grpc.ClientStream
|
||||
bufferedSends []interface{} // single message that the client can sen
|
||||
receivedGood bool // indicates whether any prior receives were successful
|
||||
wasClosedSend bool // indicates that CloseSend was closed
|
||||
parentCtx context.Context
|
||||
callOpts *options
|
||||
streamerCall func(ctx context.Context) (grpc.ClientStream, error)
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func (s *serverStreamingRetryingStream) setStream(clientStream grpc.ClientStream) {
|
||||
s.mu.Lock()
|
||||
s.ClientStream = clientStream
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
func (s *serverStreamingRetryingStream) getStream() grpc.ClientStream {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.ClientStream
|
||||
}
|
||||
|
||||
func (s *serverStreamingRetryingStream) SendMsg(m interface{}) error {
|
||||
s.mu.Lock()
|
||||
s.bufferedSends = append(s.bufferedSends, m)
|
||||
s.mu.Unlock()
|
||||
return s.getStream().SendMsg(m)
|
||||
}
|
||||
|
||||
func (s *serverStreamingRetryingStream) CloseSend() error {
|
||||
s.mu.Lock()
|
||||
s.wasClosedSend = true
|
||||
s.mu.Unlock()
|
||||
return s.getStream().CloseSend()
|
||||
}
|
||||
|
||||
func (s *serverStreamingRetryingStream) Header() (metadata.MD, error) {
|
||||
return s.getStream().Header()
|
||||
}
|
||||
|
||||
func (s *serverStreamingRetryingStream) Trailer() metadata.MD {
|
||||
return s.getStream().Trailer()
|
||||
}
|
||||
|
||||
func (s *serverStreamingRetryingStream) RecvMsg(m interface{}) error {
|
||||
attemptRetry, lastErr := s.receiveMsgAndIndicateRetry(m)
|
||||
if !attemptRetry {
|
||||
return lastErr // success or hard failure
|
||||
}
|
||||
// We start off from attempt 1, because zeroth was already made on normal SendMsg().
|
||||
for attempt := uint(1); attempt < s.callOpts.max; attempt++ {
|
||||
if err := waitRetryBackoff(attempt, s.parentCtx, s.callOpts); err != nil {
|
||||
return err
|
||||
}
|
||||
callCtx := perCallContext(s.parentCtx, s.callOpts, attempt)
|
||||
newStream, err := s.reestablishStreamAndResendBuffer(callCtx)
|
||||
if err != nil {
|
||||
// TODO(mwitkow): Maybe dial and transport errors should be retriable?
|
||||
return err
|
||||
}
|
||||
s.setStream(newStream)
|
||||
attemptRetry, lastErr = s.receiveMsgAndIndicateRetry(m)
|
||||
//fmt.Printf("Received message and indicate: %v %v\n", attemptRetry, lastErr)
|
||||
if !attemptRetry {
|
||||
return lastErr
|
||||
}
|
||||
}
|
||||
return lastErr
|
||||
}
|
||||
|
||||
func (s *serverStreamingRetryingStream) receiveMsgAndIndicateRetry(m interface{}) (bool, error) {
|
||||
s.mu.RLock()
|
||||
wasGood := s.receivedGood
|
||||
s.mu.RUnlock()
|
||||
err := s.getStream().RecvMsg(m)
|
||||
if err == nil || err == io.EOF {
|
||||
s.mu.Lock()
|
||||
s.receivedGood = true
|
||||
s.mu.Unlock()
|
||||
return false, err
|
||||
} else if wasGood {
|
||||
// previous RecvMsg in the stream succeeded, no retry logic should interfere
|
||||
return false, err
|
||||
}
|
||||
if isContextError(err) {
|
||||
if s.parentCtx.Err() != nil {
|
||||
logTrace(s.parentCtx, "grpc_retry parent context error: %v", s.parentCtx.Err())
|
||||
return false, err
|
||||
} else if s.callOpts.perCallTimeout != 0 {
|
||||
// We have set a perCallTimeout in the retry middleware, which would result in a context error if
|
||||
// the deadline was exceeded, in which case try again.
|
||||
logTrace(s.parentCtx, "grpc_retry context error from retry call")
|
||||
return true, err
|
||||
}
|
||||
}
|
||||
return isRetriable(err, s.callOpts), err
|
||||
}
|
||||
|
||||
func (s *serverStreamingRetryingStream) reestablishStreamAndResendBuffer(callCtx context.Context) (grpc.ClientStream, error) {
|
||||
s.mu.RLock()
|
||||
bufferedSends := s.bufferedSends
|
||||
s.mu.RUnlock()
|
||||
newStream, err := s.streamerCall(callCtx)
|
||||
if err != nil {
|
||||
logTrace(callCtx, "grpc_retry failed redialing new stream: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
for _, msg := range bufferedSends {
|
||||
if err := newStream.SendMsg(msg); err != nil {
|
||||
logTrace(callCtx, "grpc_retry failed resending message: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err := newStream.CloseSend(); err != nil {
|
||||
logTrace(callCtx, "grpc_retry failed CloseSend on new stream %v", err)
|
||||
return nil, err
|
||||
}
|
||||
return newStream, nil
|
||||
}
|
||||
|
||||
func waitRetryBackoff(attempt uint, parentCtx context.Context, callOpts *options) error {
|
||||
var waitTime time.Duration = 0
|
||||
if attempt > 0 {
|
||||
waitTime = callOpts.backoffFunc(parentCtx, attempt)
|
||||
}
|
||||
if waitTime > 0 {
|
||||
logTrace(parentCtx, "grpc_retry attempt: %d, backoff for %v", attempt, waitTime)
|
||||
timer := time.NewTimer(waitTime)
|
||||
select {
|
||||
case <-parentCtx.Done():
|
||||
timer.Stop()
|
||||
return contextErrToGrpcErr(parentCtx.Err())
|
||||
case <-timer.C:
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isRetriable(err error, callOpts *options) bool {
|
||||
errCode := status.Code(err)
|
||||
if isContextError(err) {
|
||||
// context errors are not retriable based on user settings.
|
||||
return false
|
||||
}
|
||||
for _, code := range callOpts.codes {
|
||||
if code == errCode {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isContextError(err error) bool {
|
||||
code := status.Code(err)
|
||||
return code == codes.DeadlineExceeded || code == codes.Canceled
|
||||
}
|
||||
|
||||
func perCallContext(parentCtx context.Context, callOpts *options, attempt uint) context.Context {
|
||||
ctx := parentCtx
|
||||
if callOpts.perCallTimeout != 0 {
|
||||
ctx, _ = context.WithTimeout(ctx, callOpts.perCallTimeout)
|
||||
}
|
||||
if attempt > 0 && callOpts.includeHeader {
|
||||
mdClone := metautils.ExtractOutgoing(ctx).Clone().Set(AttemptMetadataKey, fmt.Sprintf("%d", attempt))
|
||||
ctx = mdClone.ToOutgoing(ctx)
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
func contextErrToGrpcErr(err error) error {
|
||||
switch err {
|
||||
case context.DeadlineExceeded:
|
||||
return status.Errorf(codes.DeadlineExceeded, err.Error())
|
||||
case context.Canceled:
|
||||
return status.Errorf(codes.Canceled, err.Error())
|
||||
default:
|
||||
return status.Errorf(codes.Unknown, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func logTrace(ctx context.Context, format string, a ...interface{}) {
|
||||
tr, ok := trace.FromContext(ctx)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
tr.LazyPrintf(format, a...)
|
||||
}
|
28
vendor/github.com/grpc-ecosystem/go-grpc-middleware/util/backoffutils/backoff.go
generated
vendored
Normal file
28
vendor/github.com/grpc-ecosystem/go-grpc-middleware/util/backoffutils/backoff.go
generated
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
// Copyright 2016 Michal Witkowski. All Rights Reserved.
|
||||
// See LICENSE for licensing terms.
|
||||
|
||||
/*
|
||||
Backoff Helper Utilities
|
||||
|
||||
Implements common backoff features.
|
||||
*/
|
||||
package backoffutils
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
// JitterUp adds random jitter to the duration.
|
||||
//
|
||||
// This adds or subtracts time from the duration within a given jitter fraction.
|
||||
// For example for 10s and jitter 0.1, it will return a time within [9s, 11s])
|
||||
func JitterUp(duration time.Duration, jitter float64) time.Duration {
|
||||
multiplier := jitter * (rand.Float64()*2 - 1)
|
||||
return time.Duration(float64(duration) * (1 + multiplier))
|
||||
}
|
||||
|
||||
// ExponentBase2 computes 2^(a-1) where a >= 1. If a is 0, the result is 0.
|
||||
func ExponentBase2(a uint) uint {
|
||||
return (1 << a) >> 1
|
||||
}
|
19
vendor/github.com/grpc-ecosystem/go-grpc-middleware/util/metautils/doc.go
generated
vendored
Normal file
19
vendor/github.com/grpc-ecosystem/go-grpc-middleware/util/metautils/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
// Copyright 2016 Michal Witkowski. All Rights Reserved.
|
||||
// See LICENSE for licensing terms.
|
||||
|
||||
/*
|
||||
Package `metautils` provides convenience functions for dealing with gRPC metadata.MD objects inside
|
||||
Context handlers.
|
||||
|
||||
While the upstream grpc-go package contains decent functionality (see https://github.com/grpc/grpc-go/blob/master/Documentation/grpc-metadata.md)
|
||||
they are hard to use.
|
||||
|
||||
The majority of functions center around the NiceMD, which is a convenience wrapper around metadata.MD. For example
|
||||
the following code allows you to easily extract incoming metadata (server handler) and put it into a new client context
|
||||
metadata.
|
||||
|
||||
nmd := metautils.ExtractIncoming(serverCtx).Clone(":authorization", ":custom")
|
||||
clientCtx := nmd.Set("x-client-header", "2").Set("x-another", "3").ToOutgoing(ctx)
|
||||
*/
|
||||
|
||||
package metautils
|
126
vendor/github.com/grpc-ecosystem/go-grpc-middleware/util/metautils/nicemd.go
generated
vendored
Normal file
126
vendor/github.com/grpc-ecosystem/go-grpc-middleware/util/metautils/nicemd.go
generated
vendored
Normal file
|
@ -0,0 +1,126 @@
|
|||
// Copyright 2016 Michal Witkowski. All Rights Reserved.
|
||||
// See LICENSE for licensing terms.
|
||||
|
||||
package metautils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
// NiceMD is a convenience wrapper definiting extra functions on the metadata.
|
||||
type NiceMD metadata.MD
|
||||
|
||||
// ExtractIncoming extracts an inbound metadata from the server-side context.
|
||||
//
|
||||
// This function always returns a NiceMD wrapper of the metadata.MD, in case the context doesn't have metadata it returns
|
||||
// a new empty NiceMD.
|
||||
func ExtractIncoming(ctx context.Context) NiceMD {
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
return NiceMD(metadata.Pairs())
|
||||
}
|
||||
return NiceMD(md)
|
||||
}
|
||||
|
||||
// ExtractOutgoing extracts an outbound metadata from the client-side context.
|
||||
//
|
||||
// This function always returns a NiceMD wrapper of the metadata.MD, in case the context doesn't have metadata it returns
|
||||
// a new empty NiceMD.
|
||||
func ExtractOutgoing(ctx context.Context) NiceMD {
|
||||
md, ok := metadata.FromOutgoingContext(ctx)
|
||||
if !ok {
|
||||
return NiceMD(metadata.Pairs())
|
||||
}
|
||||
return NiceMD(md)
|
||||
}
|
||||
|
||||
// Clone performs a *deep* copy of the metadata.MD.
|
||||
//
|
||||
// You can specify the lower-case copiedKeys to only copy certain whitelisted keys. If no keys are explicitly whitelisted
|
||||
// all keys get copied.
|
||||
func (m NiceMD) Clone(copiedKeys ...string) NiceMD {
|
||||
newMd := NiceMD(metadata.Pairs())
|
||||
for k, vv := range m {
|
||||
found := false
|
||||
if len(copiedKeys) == 0 {
|
||||
found = true
|
||||
} else {
|
||||
for _, allowedKey := range copiedKeys {
|
||||
if strings.EqualFold(allowedKey, k) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
newMd[k] = make([]string, len(vv))
|
||||
copy(newMd[k], vv)
|
||||
}
|
||||
return NiceMD(newMd)
|
||||
}
|
||||
|
||||
// ToOutgoing sets the given NiceMD as a client-side context for dispatching.
|
||||
func (m NiceMD) ToOutgoing(ctx context.Context) context.Context {
|
||||
return metadata.NewOutgoingContext(ctx, metadata.MD(m))
|
||||
}
|
||||
|
||||
// ToIncoming sets the given NiceMD as a server-side context for dispatching.
|
||||
//
|
||||
// This is mostly useful in ServerInterceptors..
|
||||
func (m NiceMD) ToIncoming(ctx context.Context) context.Context {
|
||||
return metadata.NewIncomingContext(ctx, metadata.MD(m))
|
||||
}
|
||||
|
||||
// Get retrieves a single value from the metadata.
|
||||
//
|
||||
// It works analogously to http.Header.Get, returning the first value if there are many set. If the value is not set,
|
||||
// an empty string is returned.
|
||||
//
|
||||
// The function is binary-key safe.
|
||||
func (m NiceMD) Get(key string) string {
|
||||
k, _ := encodeKeyValue(key, "")
|
||||
vv, ok := m[k]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return vv[0]
|
||||
}
|
||||
|
||||
// Del retrieves a single value from the metadata.
|
||||
//
|
||||
// It works analogously to http.Header.Del, deleting all values if they exist.
|
||||
//
|
||||
// The function is binary-key safe.
|
||||
|
||||
func (m NiceMD) Del(key string) NiceMD {
|
||||
k, _ := encodeKeyValue(key, "")
|
||||
delete(m, k)
|
||||
return m
|
||||
}
|
||||
|
||||
// Set sets the given value in a metadata.
|
||||
//
|
||||
// It works analogously to http.Header.Set, overwriting all previous metadata values.
|
||||
//
|
||||
// The function is binary-key safe.
|
||||
func (m NiceMD) Set(key string, value string) NiceMD {
|
||||
k, v := encodeKeyValue(key, value)
|
||||
m[k] = []string{v}
|
||||
return m
|
||||
}
|
||||
|
||||
// Add retrieves a single value from the metadata.
|
||||
//
|
||||
// It works analogously to http.Header.Add, as it appends to any existing values associated with key.
|
||||
//
|
||||
// The function is binary-key safe.
|
||||
func (m NiceMD) Add(key string, value string) NiceMD {
|
||||
k, v := encodeKeyValue(key, value)
|
||||
m[k] = append(m[k], v)
|
||||
return m
|
||||
}
|
22
vendor/github.com/grpc-ecosystem/go-grpc-middleware/util/metautils/single_key.go
generated
vendored
Normal file
22
vendor/github.com/grpc-ecosystem/go-grpc-middleware/util/metautils/single_key.go
generated
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
// Copyright 2016 Michal Witkowski. All Rights Reserved.
|
||||
// See LICENSE for licensing terms.
|
||||
|
||||
package metautils
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
binHdrSuffix = "-bin"
|
||||
)
|
||||
|
||||
func encodeKeyValue(k, v string) (string, string) {
|
||||
k = strings.ToLower(k)
|
||||
if strings.HasSuffix(k, binHdrSuffix) {
|
||||
val := base64.StdEncoding.EncodeToString([]byte(v))
|
||||
v = string(val)
|
||||
}
|
||||
return k, v
|
||||
}
|
|
@ -197,6 +197,9 @@
|
|||
{"path":"github.com/gorilla/context","checksumSHA1":"g/V4qrXjUGG9B+e3hB+4NAYJ5Gs=","revision":"08b5f424b9271eedf6f9f0ce86cb9396ed337a42","revisionTime":"2016-08-17T18:46:32Z"},
|
||||
{"path":"github.com/gorilla/mux","checksumSHA1":"STQSdSj2FcpCf0NLfdsKhNutQT0=","revision":"e48e440e4c92e3251d812f8ce7858944dfa3331c","revisionTime":"2018-08-07T07:52:56Z"},
|
||||
{"path":"github.com/gorilla/websocket","checksumSHA1":"gr0edNJuVv4+olNNZl5ZmwLgscA=","revision":"0ec3d1bd7fe50c503d6df98ee649d81f4857c564","revisionTime":"2019-03-06T00:42:57Z"},
|
||||
{"path":"github.com/grpc-ecosystem/go-grpc-middleware/retry","checksumSHA1":"Wmzc+OYGzhkkXvwphrh/1C7TGmI=","revision":"3ce3d519df39b5289d789b3d54f00c7a19929fe4","revisionTime":"2020-02-28T13:55:17Z"},
|
||||
{"path":"github.com/grpc-ecosystem/go-grpc-middleware/util/backoffutils","checksumSHA1":"BnZAJHYhcmPNlto3WkzkWnRPlXs=","revision":"3ce3d519df39b5289d789b3d54f00c7a19929fe4","revisionTime":"2020-02-28T13:55:17Z"},
|
||||
{"path":"github.com/grpc-ecosystem/go-grpc-middleware/util/metautils","checksumSHA1":"xvSmjSRfCFmWbEs50bSDXyzRWCo=","revision":"3ce3d519df39b5289d789b3d54f00c7a19929fe4","revisionTime":"2020-02-28T13:55:17Z"},
|
||||
{"path":"github.com/hashicorp/consul-template","checksumSHA1":"R4eLvAFtqPg22sjAUysBhFfdUPs=","revision":"58aa6c608af3387d0c2bf5d028be4960be1dbe56","revisionTime":"2020-01-25T00:24:05Z","version":"v0.24.1","versionExact":"v0.24.1"},
|
||||
{"path":"github.com/hashicorp/consul-template/child","checksumSHA1":"yQfiSUOpV5BvGeztDd4fcA7qsbw=","revision":"58aa6c608af3387d0c2bf5d028be4960be1dbe56","revisionTime":"2020-01-25T00:24:05Z","version":"v0.24.1","versionExact":"v0.24.1"},
|
||||
{"path":"github.com/hashicorp/consul-template/config","checksumSHA1":"ldDPZxD2PEPY4F9MFSOG4D8FWo8=","revision":"58aa6c608af3387d0c2bf5d028be4960be1dbe56","revisionTime":"2020-01-25T00:24:05Z","version":"v0.24.1","versionExact":"v0.24.1"},
|
||||
|
|
Loading…
Reference in New Issue