go.uber.org/cadence@v1.2.9/internal/internal_retry.go (about)

     1  // Copyright (c) 2017 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package internal
    22  
    23  // All code in this file is private to the package.
    24  
    25  import (
    26  	"context"
    27  	"errors"
    28  	"time"
    29  
    30  	s "go.uber.org/cadence/.gen/go/shared"
    31  	"go.uber.org/cadence/internal/common/backoff"
    32  )
    33  
    34  const (
    35  	retryServiceOperationInitialInterval    = 20 * time.Millisecond
    36  	retryServiceOperationExpirationInterval = 60 * time.Second
    37  	retryServiceOperationBackoff            = 1.2
    38  )
    39  
    40  // Creates a retry policy which allows appropriate retries for the deadline passed in as context.
    41  // It uses the context deadline to set MaxInterval as 1/10th of context timeout
    42  // MaxInterval = Max(context_timeout/10, 20ms)
    43  // defaults to ExpirationInterval of 60 seconds, or uses context deadline as expiration interval
    44  func createDynamicServiceRetryPolicy(ctx context.Context) backoff.RetryPolicy {
    45  	timeout := retryServiceOperationExpirationInterval
    46  	if ctx != nil {
    47  		now := time.Now()
    48  		if expiration, ok := ctx.Deadline(); ok && expiration.After(now) {
    49  			timeout = expiration.Sub(now)
    50  		}
    51  	}
    52  	initialInterval := retryServiceOperationInitialInterval
    53  	maximumInterval := timeout / 10
    54  	if maximumInterval < retryServiceOperationInitialInterval {
    55  		maximumInterval = retryServiceOperationInitialInterval
    56  	}
    57  
    58  	policy := backoff.NewExponentialRetryPolicy(initialInterval)
    59  	policy.SetBackoffCoefficient(retryServiceOperationBackoff)
    60  	policy.SetMaximumInterval(maximumInterval)
    61  	policy.SetExpirationInterval(timeout)
    62  	return policy
    63  }
    64  
    65  func isServiceTransientError(err error) bool {
    66  	// check intentionally-not-retried error types via errors.As.
    67  	//
    68  	// sadly we cannot build this into a list of error values / types to range over, as that
    69  	// would turn the variable passed as the &target into e.g. an interface{} or an error,
    70  	// which is compatible with ALL errors, so all are .As(err, &target).
    71  	//
    72  	// so we're left with this mess.  it's not even generics-friendly.
    73  	// at least this pattern lets it be done inline without exposing the variable.
    74  	if target := (*s.AccessDeniedError)(nil); errors.As(err, &target) {
    75  		return false
    76  	}
    77  	if target := (*s.BadRequestError)(nil); errors.As(err, &target) {
    78  		return false
    79  	}
    80  	if target := (*s.CancellationAlreadyRequestedError)(nil); errors.As(err, &target) {
    81  		return false
    82  	}
    83  	if target := (*s.ClientVersionNotSupportedError)(nil); errors.As(err, &target) {
    84  		return false
    85  	}
    86  	if target := (*s.DomainAlreadyExistsError)(nil); errors.As(err, &target) {
    87  		return false
    88  	}
    89  	if target := (*s.DomainNotActiveError)(nil); errors.As(err, &target) {
    90  		return false
    91  	}
    92  	if target := (*s.EntityNotExistsError)(nil); errors.As(err, &target) {
    93  		return false
    94  	}
    95  	if target := (*s.FeatureNotEnabledError)(nil); errors.As(err, &target) {
    96  		return false
    97  	}
    98  	if target := (*s.LimitExceededError)(nil); errors.As(err, &target) {
    99  		return false
   100  	}
   101  	if target := (*s.QueryFailedError)(nil); errors.As(err, &target) {
   102  		return false
   103  	}
   104  	if target := (*s.WorkflowExecutionAlreadyCompletedError)(nil); errors.As(err, &target) {
   105  		return false
   106  	}
   107  	if target := (*s.WorkflowExecutionAlreadyStartedError)(nil); errors.As(err, &target) {
   108  		return false
   109  	}
   110  
   111  	// shutdowns are not retryable, of course
   112  	if errors.Is(err, errShutdown) {
   113  		return false
   114  	}
   115  
   116  	if target := (*s.ServiceBusyError)(nil); errors.As(err, &target) {
   117  		return true
   118  	}
   119  
   120  	// s.InternalServiceError
   121  	// s.ServiceBusyError (must retry after a delay, but it is transient)
   122  	// server-side-only error types (as they should not reach clients)
   123  	// and all other `error` types
   124  	return true
   125  }