Files
tasks/common/retrypolicy/retry_policy.go
pdoernerandGitHub 26fb9cc5f3 Add back 250 year max duration cap (#7337)
## What changed?
<!-- Describe what has changed in this PR -->
Adding back a cap for durations at 250 years.

## Why?
<!-- Tell your future self why have you made these changes -->
To prevent serialization errors for very large proto durations.

## How did you test it?
<!-- How have you verified this change? Tested locally? Added a unit
test? Checked in staging env? -->
New unit test
2025-02-13 21:44:17 +00:00

123 lines
4.9 KiB
Go

// The MIT License
//
// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved.
//
// Copyright (c) 2020 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package retrypolicy
import (
"fmt"
"strings"
"time"
commonpb "go.temporal.io/api/common/v1"
enumspb "go.temporal.io/api/enums/v1"
"go.temporal.io/api/serviceerror"
"go.temporal.io/server/common/primitives/timestamp"
"google.golang.org/protobuf/types/known/durationpb"
)
const (
// TimeoutFailureTypePrefix is the prefix for timeout failure types
// used in retry policy
// the actual failure type will be prefix + enums.TimeoutType.String()
// e.g. "TemporalTimeout:StartToClose" or "TemporalTimeout:Heartbeat"
TimeoutFailureTypePrefix = "TemporalTimeout:"
)
// DefaultRetrySettings indicates what the "default" retry settings
// are if it is not specified on an Activity or for any unset fields
// if a policy is explicitly set on a workflow
type DefaultRetrySettings struct {
InitialInterval time.Duration `mapstructure:"InitialIntervalInSeconds"`
MaximumIntervalCoefficient float64
BackoffCoefficient float64
MaximumAttempts int32
}
var DefaultDefaultRetrySettings = DefaultRetrySettings{
InitialInterval: time.Second,
MaximumIntervalCoefficient: 100.0,
BackoffCoefficient: 2.0,
MaximumAttempts: 0,
}
// EnsureDefaults ensures the policy subfields, if not explicitly set, are set to the specified defaults
func EnsureDefaults(originalPolicy *commonpb.RetryPolicy, defaultSettings DefaultRetrySettings) {
if originalPolicy.GetMaximumAttempts() == 0 {
originalPolicy.MaximumAttempts = defaultSettings.MaximumAttempts
}
if timestamp.DurationValue(originalPolicy.GetInitialInterval()) == 0 {
originalPolicy.InitialInterval = durationpb.New(defaultSettings.InitialInterval)
}
if timestamp.DurationValue(originalPolicy.GetMaximumInterval()) == 0 {
originalPolicy.MaximumInterval = durationpb.New(time.Duration(defaultSettings.MaximumIntervalCoefficient) * timestamp.DurationValue(originalPolicy.GetInitialInterval()))
}
if originalPolicy.GetBackoffCoefficient() == 0 {
originalPolicy.BackoffCoefficient = defaultSettings.BackoffCoefficient
}
}
// Validate validates a retry policy
func Validate(policy *commonpb.RetryPolicy) error {
if policy == nil {
// nil policy is valid which means no retry
return nil
}
if policy.GetMaximumAttempts() == 1 {
// One maximum attempt effectively disable retries. Validating the
// rest of the arguments is pointless
return nil
}
if err := timestamp.ValidateAndCapProtoDuration(policy.GetInitialInterval()); err != nil {
return serviceerror.NewInvalidArgument(fmt.Sprintf("invalid InitialInterval set on retry policy: %v", err))
}
if policy.GetBackoffCoefficient() < 1 {
return serviceerror.NewInvalidArgument("BackoffCoefficient cannot be less than 1 on retry policy.")
}
if err := timestamp.ValidateAndCapProtoDuration(policy.GetMaximumInterval()); err != nil {
return serviceerror.NewInvalidArgument(fmt.Sprintf("invalid MaximumInterval set on retry policy: %v", err))
}
if timestamp.DurationValue(policy.GetMaximumInterval()) > 0 && timestamp.DurationValue(policy.GetMaximumInterval()) < timestamp.DurationValue(policy.GetInitialInterval()) {
return serviceerror.NewInvalidArgument("MaximumInterval cannot be less than InitialInterval on retry policy.")
}
if policy.GetMaximumAttempts() < 0 {
return serviceerror.NewInvalidArgument("MaximumAttempts cannot be negative on retry policy.")
}
for _, nrt := range policy.NonRetryableErrorTypes {
if strings.HasPrefix(nrt, TimeoutFailureTypePrefix) {
timeoutTypeValue := nrt[len(TimeoutFailureTypePrefix):]
timeoutType, err := enumspb.TimeoutTypeFromString(timeoutTypeValue)
if err != nil || enumspb.TimeoutType(timeoutType) == enumspb.TIMEOUT_TYPE_UNSPECIFIED {
return serviceerror.NewInvalidArgument(fmt.Sprintf("Invalid timeout type value: %v.", timeoutTypeValue))
}
}
}
return nil
}