-
Notifications
You must be signed in to change notification settings - Fork 507
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[FAB-7508] Add retries to channel client
Change-Id: I3bceb4913bfc4e2d539031ba46055a8d69edc304 Signed-off-by: Divyank Katira <[email protected]>
- Loading branch information
Showing
9 changed files
with
322 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
/* | ||
Copyright SecureKey Technologies Inc. All Rights Reserved. | ||
SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package retry | ||
|
||
import ( | ||
"time" | ||
|
||
"github.com/hyperledger/fabric-sdk-go/pkg/status" | ||
"github.com/hyperledger/fabric-sdk-go/third_party/github.com/hyperledger/fabric/protos/common" | ||
pb "github.com/hyperledger/fabric-sdk-go/third_party/github.com/hyperledger/fabric/protos/peer" | ||
grpcCodes "google.golang.org/grpc/codes" | ||
) | ||
|
||
const ( | ||
// DefaultAttempts number of retry attempts made by default | ||
DefaultAttempts = 3 | ||
// DefaultInitialBackoff default initial backoff | ||
DefaultInitialBackoff = 500 * time.Millisecond | ||
// DefaultMaxBackoff default maximum backoff | ||
DefaultMaxBackoff = 60 * time.Second | ||
// DefaultBackoffFactor default backoff factor | ||
DefaultBackoffFactor = 2.0 | ||
) | ||
|
||
// DefaultOpts default retry options | ||
var DefaultOpts = Opts{ | ||
Attempts: DefaultAttempts, | ||
InitialBackoff: DefaultInitialBackoff, | ||
MaxBackoff: DefaultMaxBackoff, | ||
BackoffFactor: DefaultBackoffFactor, | ||
RetryableCodes: DefaultRetryableCodes, | ||
} | ||
|
||
// DefaultRetryableCodes these are the error codes, grouped by source of error, | ||
// that are considered to be transient error conditions by default | ||
var DefaultRetryableCodes = map[status.Group][]status.Code{ | ||
status.EndorserClientStatus: []status.Code{ | ||
status.EndorsementMismatch, | ||
}, | ||
status.EndorserServerStatus: []status.Code{ | ||
status.Code(common.Status_SERVICE_UNAVAILABLE), | ||
status.Code(common.Status_INTERNAL_SERVER_ERROR), | ||
}, | ||
status.OrdererServerStatus: []status.Code{ | ||
status.Code(common.Status_SERVICE_UNAVAILABLE), | ||
status.Code(common.Status_INTERNAL_SERVER_ERROR), | ||
}, | ||
status.EventServerStatus: []status.Code{ | ||
status.Code(pb.TxValidationCode_DUPLICATE_TXID), | ||
status.Code(pb.TxValidationCode_ENDORSEMENT_POLICY_FAILURE), | ||
status.Code(pb.TxValidationCode_MVCC_READ_CONFLICT), | ||
status.Code(pb.TxValidationCode_PHANTOM_READ_CONFLICT), | ||
}, | ||
// TODO: gRPC introduced retries in v1.8.0. This can be replaced with the | ||
// gRPC fail fast option, once available | ||
status.GRPCTransportStatus: []status.Code{ | ||
status.Code(grpcCodes.Unavailable), | ||
}, | ||
} | ||
|
||
// ChannelClientRetryableCodes are the suggested codes that should be treated as | ||
// transient by fabric-sdk-go/api/apitxn.ChannelClient | ||
var ChannelClientRetryableCodes = map[status.Group][]status.Code{ | ||
status.EndorserClientStatus: []status.Code{ | ||
status.ConnectionFailed, status.EndorsementMismatch, | ||
}, | ||
status.EndorserServerStatus: []status.Code{ | ||
status.Code(common.Status_SERVICE_UNAVAILABLE), | ||
status.Code(common.Status_INTERNAL_SERVER_ERROR), | ||
}, | ||
status.OrdererClientStatus: []status.Code{ | ||
status.ConnectionFailed, | ||
}, | ||
status.OrdererServerStatus: []status.Code{ | ||
status.Code(common.Status_SERVICE_UNAVAILABLE), | ||
status.Code(common.Status_INTERNAL_SERVER_ERROR), | ||
}, | ||
status.EventServerStatus: []status.Code{ | ||
status.Code(pb.TxValidationCode_DUPLICATE_TXID), | ||
status.Code(pb.TxValidationCode_ENDORSEMENT_POLICY_FAILURE), | ||
status.Code(pb.TxValidationCode_MVCC_READ_CONFLICT), | ||
status.Code(pb.TxValidationCode_PHANTOM_READ_CONFLICT), | ||
}, | ||
// TODO: gRPC introduced retries in v1.8.0. This can be replaced with the | ||
// gRPC fail fast option, once available | ||
status.GRPCTransportStatus: []status.Code{ | ||
status.Code(grpcCodes.Unavailable), | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
/* | ||
Copyright SecureKey Technologies Inc. All Rights Reserved. | ||
SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
// Package retry provides retransmission capabilities to fabric-sdk-go | ||
package retry | ||
|
||
import ( | ||
"time" | ||
|
||
"github.com/hyperledger/fabric-sdk-go/pkg/status" | ||
) | ||
|
||
// Opts defines the retry parameters | ||
type Opts struct { | ||
// Attempts the number retry attempts | ||
Attempts int | ||
// InitialBackoff the backoff interval for the first retry attempt | ||
InitialBackoff time.Duration | ||
// MaxBackoff the maximum backoff interval for any retry attempt | ||
MaxBackoff time.Duration | ||
// BackoffFactor the factor by which the InitialBackoff is exponentially | ||
// incremented for consecutive retry attempts. | ||
// For example, a backoff factor of 2.5 will result in a backoff of | ||
// InitialBackoff * 2.5 * 2.5 on the second attempt. | ||
BackoffFactor float64 | ||
// RetryableCodes defines the status codes, mapped by group, returned by fabric-sdk-go | ||
// that warrant a retry. This will default to retry.DefaultRetryableCodes. | ||
RetryableCodes map[status.Group][]status.Code | ||
} | ||
|
||
// Handler retry handler interface decides whether a retry is required for the given | ||
// error | ||
type Handler interface { | ||
Required(err error) bool | ||
} | ||
|
||
// impl retry Handler implementation | ||
type impl struct { | ||
opts Opts | ||
retries int | ||
} | ||
|
||
// New retry Handler with the given opts | ||
func New(opts Opts) Handler { | ||
if len(opts.RetryableCodes) == 0 { | ||
opts.RetryableCodes = DefaultRetryableCodes | ||
} | ||
return &impl{opts: opts} | ||
} | ||
|
||
// WithDefaults new retry Handler with default opts | ||
func WithDefaults() Handler { | ||
return &impl{opts: DefaultOpts} | ||
} | ||
|
||
// WithAttempts new retry Handler with given attempts. Other opts are set to default. | ||
func WithAttempts(attempts int) Handler { | ||
opts := DefaultOpts | ||
opts.Attempts = attempts | ||
return &impl{opts: opts} | ||
} | ||
|
||
// Required determines if retry is required for the given error | ||
// Note: backoffs are implemented behind this interface | ||
func (i *impl) Required(err error) bool { | ||
if i.retries == i.opts.Attempts { | ||
return false | ||
} | ||
|
||
s, ok := status.FromError(err) | ||
if ok && i.isRetryable(s.Group, s.Code) { | ||
time.Sleep(i.backoffPeriod()) | ||
i.retries++ | ||
return true | ||
} | ||
|
||
return false | ||
} | ||
|
||
// backoffPeriod calculates the backoff duration based on the provided opts | ||
func (i *impl) backoffPeriod() time.Duration { | ||
backoff, max := float64(i.opts.InitialBackoff), float64(i.opts.MaxBackoff) | ||
for j := 0; j < i.retries && backoff < max; j++ { | ||
backoff *= i.opts.BackoffFactor | ||
} | ||
if backoff > max { | ||
backoff = max | ||
} | ||
|
||
return time.Duration(backoff) | ||
} | ||
|
||
// isRetryable determines if the given status is configured to be retryable | ||
func (i *impl) isRetryable(g status.Group, c int32) bool { | ||
for group, codes := range i.opts.RetryableCodes { | ||
if g != group { | ||
continue | ||
} | ||
for _, code := range codes { | ||
if status.Code(c) == code { | ||
return true | ||
} | ||
} | ||
} | ||
return false | ||
} |
Oops, something went wrong.