go.temporal.io/server/common/util.go
791 LOC · 378 covered · 413 uncovered · 110 ranges · 18071 concepts · 73 introducers · 8970 tests
File neighbourhood
The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.
Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file
In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.
Graph controls are ready.
Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.
package common
import (
"context"
"errors"
"fmt"
"math/rand"
"reflect"
"strings"
"sync"
"time"
"github.com/dgryski/go-farm"
commonpb "go.temporal.io/api/common/v1"
enumspb "go.temporal.io/api/enums/v1"
"go.temporal.io/api/serviceerror"
"go.temporal.io/api/workflowservice/v1"
"go.temporal.io/server/api/historyservice/v1"
"go.temporal.io/server/api/matchingservice/v1"
workflowspb "go.temporal.io/server/api/workflow/v1"
"go.temporal.io/server/common/backoff"
"go.temporal.io/server/common/log"
"go.temporal.io/server/common/log/tag"
"go.temporal.io/server/common/metrics"
"go.temporal.io/server/common/primitives/timestamp"
serviceerrors "go.temporal.io/server/common/serviceerror"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/encoding/prototext"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protopath"
"google.golang.org/protobuf/reflect/protorange"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/types/known/durationpb"
"google.golang.org/protobuf/types/known/timestamppb"
)
const (
persistenceClientRetryInitialInterval = 50 * time.Millisecond
persistenceClientRetryMaxAttempts = 2
frontendClientRetryInitialInterval = 200 * time.Millisecond
frontendClientRetryMaxAttempts = 2
historyClientRetryInitialInterval = 50 * time.Millisecond
historyClientRetryMaxAttempts = 2
matchingClientRetryInitialInterval = 1000 * time.Millisecond
matchingClientRetryMaxAttempts = 2
frontendHandlerRetryInitialInterval = 200 * time.Millisecond
frontendHandlerRetryMaxInterval = time.Second
frontendHandlerRetryMaxAttempts = 2
historyHandlerRetryInitialInterval = 50 * time.Millisecond
historyHandlerRetryMaxAttempts = 2
matchingHandlerRetryInitialInterval = 1000 * time.Millisecond
matchingHandlerRetryMaxAttempts = 2
readTaskRetryInitialInterval = 50 * time.Millisecond
readTaskRetryMaxInterval = 1 * time.Second
readTaskRetryExpirationInterval = backoff.NoInterval
completeTaskRetryInitialInterval = 100 * time.Millisecond
completeTaskRetryMaxInterval = 1 * time.Second
completeTaskRetryMaxAttempts = 10
taskRescheduleInitialInterval = 1 * time.Second
taskRescheduleBackoffCoefficient = 1.1
taskRescheduleMaxInterval = 3 * time.Minute
taskNotReadyRescheduleInitialInterval = 3 * time.Second
taskNotReadyRescheduleBackoffCoefficient = 1.5
taskNotReadyRescheduleMaxInterval = 3 * time.Minute
// dependencyTaskNotCompletedRescheduleInitialInterval is lower than the interval the ack level most queues are
// updated at, which can lead to tasks being retried more frequently than they should be. If this becomes an issue,
// we should consider increasing this interval.
dependencyTaskNotCompletedRescheduleInitialInterval = 3 * time.Second
dependencyTaskNotCompletedRescheduleBackoffCoefficient = 1.5
dependencyTaskNotCompletedRescheduleMaxInterval = 3 * time.Minute
taskResourceExhaustedRescheduleInitialInterval = 3 * time.Second
taskResourceExhaustedRescheduleBackoffCoefficient = 1.5
taskResourceExhaustedRescheduleMaxInterval = 5 * time.Minute
sdkClientFactoryRetryInitialInterval = 200 * time.Millisecond
sdkClientFactoryRetryMaxInterval = 5 * time.Second
sdkClientFactoryRetryExpirationInterval = time.Minute
contextExpireThreshold = 10 * time.Millisecond
// FailureReasonActivityTimeout is failureReason for when an activity times out, with %v as the timeout type.
FailureReasonActivityTimeout = "activity %v timeout"
// FailureReasonActivityRetryScheduleToCloseTimeout is failureReason for when an activity retry cannot be scheduled before its schedule-to-close timeout.
FailureReasonActivityRetryScheduleToCloseTimeout = "Not enough time to schedule next retry before activity ScheduleToClose timeout, giving up retrying"
// FailureReasonCompleteResultExceedsLimit is failureReason for complete result exceeds limit
FailureReasonCompleteResultExceedsLimit = "Complete result exceeds size limit."
// FailureReasonFailureDetailsExceedsLimit is failureReason for failure details exceeds limit
FailureReasonFailureExceedsLimit = "Failure exceeds size limit."
// FailureReasonCancelDetailsExceedsLimit is failureReason for cancel details exceeds limit
FailureReasonCancelDetailsExceedsLimit = "Cancel details exceed size limit."
// FailureReasonHeartbeatExceedsLimit is failureReason for heartbeat exceeds limit
FailureReasonHeartbeatExceedsLimit = "Heartbeat details exceed size limit."
// FailureReasonHistorySizeExceedsLimit is reason to fail workflow when history size exceeds limit
FailureReasonHistorySizeExceedsLimit = "Workflow history size exceeds limit."
// FailureReasonHistorySizeExceedsLimit is reason to fail workflow when history count exceeds limit
FailureReasonHistoryCountExceedsLimit = "Workflow history count exceeds limit."
// FailureReasonMutableStateSizeExceedsLimit is reason to fail workflow when mutable state size exceeds limit
FailureReasonMutableStateSizeExceedsLimit = "Workflow mutable state size exceeds limit."
// FailureReasonTransactionSizeExceedsLimit is the failureReason for when transaction cannot be committed because it exceeds size limit
FailureReasonTransactionSizeExceedsLimit = "Transaction size exceeds limit."
// FailureReasonWorkflowTerminationDueToVersionConflict is the failureReason for when workflow is terminated due to version conflict
FailureReasonWorkflowTerminationDueToVersionConflict = "Terminate Workflow Due To Version Conflict."
)
var (
// ErrBlobSizeExceedsLimit is error for event blob size exceeds limit
ErrBlobSizeExceedsLimit = serviceerror.NewInvalidArgument("Blob data size exceeds limit.")
// ErrMemoSizeExceedsLimit is error for memo size exceeds limit
ErrMemoSizeExceedsLimit = serviceerror.NewInvalidArgument("Memo size exceeds limit.")
// ErrContextTimeoutTooShort is error for setting a very short context timeout when calling a long poll API
ErrContextTimeoutTooShort = serviceerror.NewFailedPrecondition("Context timeout is too short.")
// ErrContextTimeoutNotSet is error for not setting a context timeout when calling a long poll API
ErrContextTimeoutNotSet = serviceerror.NewInvalidArgument("Context timeout is not set.")
)
var (
// ErrNamespaceHandover is error indicating namespace is in handover state and cannot process request.
ErrNamespaceHandover = serviceerror.NewUnavailablef("Namespace replication in %s state.", enumspb.REPLICATION_STATE_HANDOVER)
)
// AwaitWaitGroup calls Wait on the given wait
// Returns true if the Wait() call succeeded before the timeout
// Returns false if the Wait() did not return before the timeout
return BlockWithTimeout(wg.Wait, timeout)
}
// BlockWithTimeout invokes fn and waits for it to complete until the timeout.
// Returns true if the call completed before the timeout, otherwise returns false.
// fn is expected to be a blocking call and will continue to occupy a goroutine until it finally completes.
doneC := make(chan struct{})
go func() {
fn()
close(doneC)
}()
defer timer.Stop()
select {
case <-doneC:
return true
case <-timer.C:
return false
}
}
// CreatePersistenceClientRetryPolicy creates a retry policy for calls to persistence
return backoff.NewExponentialRetryPolicy(persistenceClientRetryInitialInterval).
WithMaximumAttempts(persistenceClientRetryMaxAttempts)
}
// CreateFrontendClientRetryPolicy creates a retry policy for calls to frontend service
return backoff.NewExponentialRetryPolicy(frontendClientRetryInitialInterval).
WithMaximumAttempts(frontendClientRetryMaxAttempts)
}
// CreateHistoryClientRetryPolicy creates a retry policy for calls to history service.
// When retryUnboundedOnSystemResourceExhausted returns true, system-scoped ResourceExhausted
// errors retry past the historyClientRetryMaxAttempts cap, bounded only by the policy's
// default 1-minute expiration interval and the caller's context. Other errors (and all
// errors when the flag is off) follow the standard cap.
func CreateHistoryClientRetryPolicy(retryUnboundedOnSystemResourceExhausted func() bool) backoff.RetryPolicy {
util.go ×1
return newClientRetryPolicy(historyClientRetryInitialInterval, historyClientRetryMaxAttempts, retryUnboundedOnSystemResourceExhausted)
}
// CreateMatchingClientRetryPolicy creates a retry policy for calls to matching service.
// When retryUnboundedOnSystemResourceExhausted returns true, system-scoped ResourceExhausted
// errors retry past the matchingClientRetryMaxAttempts cap, bounded only by the policy's
// default 1-minute expiration interval and the caller's context. Other errors (and all
// errors when the flag is off) follow the standard cap.
func CreateMatchingClientRetryPolicy(retryUnboundedOnSystemResourceExhausted func() bool) backoff.RetryPolicy {
util.go ×1
return newClientRetryPolicy(matchingClientRetryInitialInterval, matchingClientRetryMaxAttempts, retryUnboundedOnSystemResourceExhausted)
}
func newClientRetryPolicy(initialInterval time.Duration, maxAttempts int, retryUnboundedOnSystemResourceExhausted func() bool) backoff.RetryPolicy {
util.go ×1
capped := backoff.NewExponentialRetryPolicy(initialInterval).
WithMaximumAttempts(maxAttempts)
// No max-attempts cap; bounded by the default 1-minute expiration interval
// and the caller's context.
extended := backoff.NewExponentialRetryPolicy(initialInterval)
predicate := func(err error) bool {
return retryUnboundedOnSystemResourceExhausted() && isSystemResourceExhausted(err)
}
return backoff.NewConditionalRetryPolicy(predicate, extended, capped)
}
if re, ok := err.(*serviceerror.ResourceExhausted); ok {
}
}
// CreateMatchingClientLongPollRetryPolicy creates a retry policy for poll calls to matching service
// no maximum attempts, using default expiration interval of 1 minute
return backoff.NewExponentialRetryPolicy(matchingClientRetryInitialInterval)
}
// CreateFrontendHandlerRetryPolicy creates a retry policy for calls to frontend service
return backoff.NewExponentialRetryPolicy(frontendHandlerRetryInitialInterval).
WithMaximumInterval(frontendHandlerRetryMaxInterval).
WithMaximumAttempts(frontendHandlerRetryMaxAttempts)
}
// CreateHistoryHandlerRetryPolicy creates a retry policy for calls to history service
return backoff.NewExponentialRetryPolicy(historyHandlerRetryInitialInterval).
WithMaximumAttempts(historyHandlerRetryMaxAttempts)
}
// CreateMatchingHandlerRetryPolicy creates a retry policy for calls to matching service
return backoff.NewExponentialRetryPolicy(matchingHandlerRetryInitialInterval).
WithMaximumAttempts(matchingHandlerRetryMaxAttempts)
}
// CreateReadTaskRetryPolicy creates a retry policy for loading background tasks
return backoff.NewExponentialRetryPolicy(readTaskRetryInitialInterval).
WithMaximumInterval(readTaskRetryMaxInterval).
WithExpirationInterval(readTaskRetryExpirationInterval)
}
// CreateCompleteTaskRetryPolicy creates a retry policy for completing background tasks
func CreateCompleteTaskRetryPolicy() backoff.RetryPolicy {
return backoff.NewExponentialRetryPolicy(completeTaskRetryInitialInterval).
WithMaximumInterval(completeTaskRetryMaxInterval).
WithMaximumAttempts(completeTaskRetryMaxAttempts)
}
// CreateTaskReschedulePolicy creates a retry policy for rescheduling task with errors not equal to ErrTaskRetry
return backoff.NewExponentialRetryPolicy(taskRescheduleInitialInterval).
WithBackoffCoefficient(taskRescheduleBackoffCoefficient).
WithMaximumInterval(taskRescheduleMaxInterval).
WithExpirationInterval(backoff.NoInterval)
}
// CreateDependencyTaskNotCompletedReschedulePolicy creates a retry policy for rescheduling task with
// ErrDependencyTaskNotCompleted
return backoff.NewExponentialRetryPolicy(dependencyTaskNotCompletedRescheduleInitialInterval).
WithBackoffCoefficient(dependencyTaskNotCompletedRescheduleBackoffCoefficient).
WithMaximumInterval(dependencyTaskNotCompletedRescheduleMaxInterval).
WithExpirationInterval(backoff.NoInterval)
}
// CreateTaskNotReadyReschedulePolicy creates a retry policy for rescheduling task with ErrTaskRetry
return backoff.NewExponentialRetryPolicy(taskNotReadyRescheduleInitialInterval).
WithBackoffCoefficient(taskNotReadyRescheduleBackoffCoefficient).
WithMaximumInterval(taskNotReadyRescheduleMaxInterval).
WithExpirationInterval(backoff.NoInterval)
}
// CreateTaskResourceExhaustedReschedulePolicy creates a retry policy for rescheduling task with resource exhausted error
return backoff.NewExponentialRetryPolicy(taskResourceExhaustedRescheduleInitialInterval).
WithBackoffCoefficient(taskResourceExhaustedRescheduleBackoffCoefficient).
WithMaximumInterval(taskResourceExhaustedRescheduleMaxInterval).
WithExpirationInterval(backoff.NoInterval)
}
// CreateSdkClientFactoryRetryPolicy creates a retry policy to handle SdkClientFactory NewClient when frontend service is not ready
return backoff.NewExponentialRetryPolicy(sdkClientFactoryRetryInitialInterval).
WithMaximumInterval(sdkClientFactoryRetryMaxInterval).
WithExpirationInterval(sdkClientFactoryRetryExpirationInterval)
}
// IsPersistenceTransientError checks if the error is a transient persistence error
switch err.(type) {
case *serviceerror.Unavailable,
return true
}
}
// IsServiceTransientError checks if the error is a retryable error.
switch err.(type) {
case *serviceerror.NotFound,
*serviceerror.NamespaceNotFound,
*serviceerror.InvalidArgument,
*serviceerror.NamespaceNotActive,
return false
}
}
return false
}
}
// IsContextDeadlineExceededErr checks if the error is context.DeadlineExceeded or serviceerror.DeadlineExceeded error
var deadlineExceededSvcErr *serviceerror.DeadlineExceeded
return errors.Is(err, context.DeadlineExceeded) ||
errors.As(err, &deadlineExceededSvcErr)
}
// IsContextCanceledErr checks if the error is context.Canceled or serviceerror.Canceled error
var canceledSvcErr *serviceerror.Canceled
return errors.Is(err, context.Canceled) ||
errors.As(err, &canceledSvcErr)
}
// IsServiceClientTransientError checks if the error is a transient error.
if IsServiceHandlerRetryableError(err) {
}
}
case *serviceerrors.ShardOwnershipLost,
*serviceerrors.StalePartitionCounts:
return true
}
}
if IsNamespaceHandoverError(err) {
}
case *serviceerror.Internal,
return true
for _, opErr := range err.OperationErrors() {
if opErr != nil && IsServiceHandlerRetryableError(opErr) {
}
}
}
}
return err.Error() == ErrNamespaceHandover.Error()
}
func IsStickyWorkerUnavailable(err error) bool {
switch err.(type) {
case *serviceerrors.StickyWorkerUnavailable:
return true
}
return false
}
// IsResourceExhausted checks if the error is a service busy error.
switch err.(type) {
return true
}
}
// IsInternalError checks if the error is an internal error.
var internalErr *serviceerror.Internal
return errors.As(err, &internalErr)
}
// IsNotFoundError checks if the error is a not found error.
var notFoundErr *serviceerror.NotFound
return errors.As(err, ¬FoundErr)
}
if err != nil {
return fmt.Sprintf("%08x", farm.Fingerprint32([]byte(err.Error())))
}
return "00000000"
}
// WorkflowIDToHistoryShard is used to map namespaceID-workflowID pair to a shardID.
// TODO: rename to BusinessIDToHistoryShard.
func WorkflowIDToHistoryShard(
namespaceID string,
workflowID string,
numberOfShards int32,
idBytes := []byte(namespaceID + "_" + workflowID)
hash := farm.Fingerprint32(idBytes)
return int32(hash%uint32(numberOfShards)) + 1 // ShardID starts with 1
}
func MapShardID(
sourceShardCount int32,
targetShardCount int32,
sourceShardID int32,
if sourceShardCount%targetShardCount != 0 && targetShardCount%sourceShardCount != 0 {
panic(fmt.Sprintf("cannot map shard ID between source & target shard count: %v vs %v",
sourceShardCount, targetShardCount))
}
if sourceShardCount < targetShardCount {
// 0-3
// 0-15
// 0 -> 0, 4, 8, 12
// 1 -> 1, 5, 9, 13
// 2 -> 2, 6, 10, 14
// 3 -> 3, 7, 11, 15
// 4x
ratio := targetShardCount / sourceShardCount
targetShardIDs := make([]int32, ratio)
for i := range targetShardIDs {
targetShardIDs[i] = sourceShardID + int32(i)*sourceShardCount + 1
}
return targetShardIDs
// many to one
return []int32{(sourceShardID % targetShardCount) + 1}
} else {
return []int32{sourceShardID + 1}
}
}
func VerifyShardIDMapping(
thisShardCount int32,
thatShardCount int32,
thisShardID int32,
thatShardID int32,
if thisShardCount%thatShardCount != 0 && thatShardCount%thisShardCount != 0 {
panic(fmt.Sprintf("cannot verify shard ID mapping between diff shard count: %v vs %v",
thisShardCount, thatShardCount))
}
if thisShardID%shardCountMin == thatShardID%shardCountMin {
return nil
}
"shard ID mapping verification failed; shard count: %v vs %v, shard ID: %v vs %v",
thisShardCount, thatShardCount,
thisShardID, thatShardID,
)
}
func PrettyPrint[T proto.Message](msgs []T, header ...string) {
var sb strings.Builder
_, _ = sb.WriteString("==========================================================================\n")
for _, h := range header {
_, _ = sb.WriteString(h)
_, _ = sb.WriteRune('\n')
}
_, _ = sb.WriteString("--------------------------------------------------------------------------\n")
for _, m := range msgs {
bs, _ := prototext.Marshal(m)
sb.Write(bs)
sb.WriteRune('\n')
}
fmt.Print(sb.String())
}
// IsValidContext checks that the thrift context is not expired on cancelled.
// Returns nil if the context is still valid. Otherwise, returns the result of
// ctx.Err()
ch := ctx.Done()
if ch != nil {
return ctx.Err()
return nil
}
}
if ok && time.Until(deadline) < contextExpireThreshold {
return context.DeadlineExceeded
}
}
// GenerateRandomString is used for generate test string
letterRunes := []rune("random")
b := make([]rune, n)
for i := range b {
b[i] = letterRunes[rand.Intn(len(letterRunes))]
}
return string(b)
}
// CreateMatchingPollWorkflowTaskQueueResponse create response for matching's PollWorkflowTaskQueue
func CreateMatchingPollWorkflowTaskQueueResponse(historyResponse *historyservice.RecordWorkflowTaskStartedResponse, workflowExecution *commonpb.WorkflowExecution, token []byte) *matchingservice.PollWorkflowTaskQueueResponseWithRawHistory {
request_response.pb.go ×6
matchingResp := &matchingservice.PollWorkflowTaskQueueResponseWithRawHistory{
TaskToken: token,
WorkflowExecution: workflowExecution,
WorkflowType: historyResponse.WorkflowType,
PreviousStartedEventId: historyResponse.PreviousStartedEventId,
StartedEventId: historyResponse.StartedEventId,
Attempt: historyResponse.GetAttempt(),
NextEventId: historyResponse.NextEventId,
StickyExecutionEnabled: historyResponse.StickyExecutionEnabled,
TransientWorkflowTask: historyResponse.TransientWorkflowTask,
WorkflowExecutionTaskQueue: historyResponse.WorkflowExecutionTaskQueue,
BranchToken: historyResponse.BranchToken,
ScheduledTime: historyResponse.ScheduledTime,
StartedTime: historyResponse.StartedTime,
Queries: historyResponse.Queries,
Messages: historyResponse.Messages,
History: historyResponse.History,
NextPageToken: historyResponse.NextPageToken,
RawHistory: historyResponse.RawHistoryBytes,
}
return matchingResp
}
// CreateHistoryStartWorkflowRequest create a start workflow request for history.
// Assumes startRequest is valid. See frontend workflow_handler for detailed validation logic.
func CreateHistoryStartWorkflowRequest(
namespaceID string,
startRequest *workflowservice.StartWorkflowExecutionRequest,
parentExecutionInfo *workflowspb.ParentExecutionInfo,
rootExecutionInfo *workflowspb.RootExecutionInfo,
now time.Time,
// We include the original startRequest in the forwarded request to History, but
// we don't want to send workflow payloads twice. We deep copy to a new struct,
// rather than mutate the request, to accommodate internal retries.
if startRequest.ContinuedFailure != nil || startRequest.LastCompletionResult != nil {
}
NamespaceId: namespaceID,
StartRequest: startRequest,
ContinueAsNewInitiator: enumspb.CONTINUE_AS_NEW_INITIATOR_UNSPECIFIED,
Attempt: 1,
ParentExecutionInfo: parentExecutionInfo,
FirstWorkflowTaskBackoff: durationpb.New(backoff.GetBackoffForNextScheduleNonNegative(startRequest.GetCronSchedule(), now, now)),
ContinuedFailure: startRequest.ContinuedFailure,
LastCompletionResult: startRequest.LastCompletionResult,
RootExecutionInfo: rootExecutionInfo,
VersioningOverride: startRequest.GetVersioningOverride(),
}
startRequest.ContinuedFailure = nil
startRequest.LastCompletionResult = nil
if timestamp.DurationValue(startRequest.GetWorkflowExecutionTimeout()) > 0 {
deadline := now.Add(timestamp.DurationValue(startRequest.GetWorkflowExecutionTimeout()))
util.go ×1
histRequest.WorkflowExecutionExpirationTime = timestamppb.New(deadline.Round(time.Millisecond))
}
// CronSchedule and WorkflowStartDelay should not both be set on the same request
histRequest.ContinueAsNewInitiator = enumspb.CONTINUE_AS_NEW_INITIATOR_CRON_SCHEDULE
handler.go ×25
}
histRequest.FirstWorkflowTaskBackoff = startRequest.GetWorkflowStartDelay()
}
}
// CheckEventBlobSizeLimit checks if a blob data exceeds limits. It logs a warning if it exceeds warnLimit,
// and return ErrBlobSizeExceedsLimit if it exceeds errorLimit.
func CheckEventBlobSizeLimit(
actualSize int,
warnLimit int,
errorLimit int,
namespace string,
workflowID string,
runID string,
metricsHandler metrics.Handler,
logger log.Logger,
operation string,
metrics.EventBlobSize.With(metricsHandler).Record(int64(actualSize), metrics.OperationTag(operation))
if actualSize > warnLimit {
logger.Warn("Blob data size exceeds the warning limit.",
tag.WorkflowNamespace(namespace), // TODO: Not necessarily a "workflow" namespace, fix the tag.
tag.WorkflowID(workflowID), // TODO: this should be entity ID and we need an archetype too.
tag.WorkflowRunID(runID), // TODO: not necessarily a workflow run ID, fix the tag.
tag.WorkflowSize(int64(actualSize)),
tag.BlobSizeViolationOperation(operation))
}
metrics.BlobSizeError.With(metricsHandler).Record(1, metrics.OperationTag(operation))
util.go ×1
return ErrBlobSizeExceedsLimit
}
}
}
// ValidateLongPollContextTimeout checks if the context timeout for a long poll handler is too short or below a normal value.
// If the timeout is not set or too short, it logs an error, and returns ErrContextTimeoutNotSet or ErrContextTimeoutTooShort
// accordingly. If the timeout is only below a normal value, it just logs an info and returns nil.
func ValidateLongPollContextTimeout(
ctx context.Context,
handlerName string,
logger log.Logger,
deadline, err := ValidateLongPollContextTimeoutIsSet(ctx, handlerName, logger)
if err != nil {
}
if timeout < MinLongPollTimeout {
logger.Error("Context timeout is too short for long poll API.",
tag.WorkflowHandlerName(handlerName), tag.Error(err), tag.WorkflowPollContextTimeout(timeout))
return err
}
logger.Warn("Context timeout is lower than critical value for long poll API.",
tag.WorkflowHandlerName(handlerName), tag.WorkflowPollContextTimeout(timeout))
}
}
// ValidateLongPollContextTimeoutIsSet checks if the context timeout is set for long poll requests.
func ValidateLongPollContextTimeoutIsSet(
ctx context.Context,
handlerName string,
logger log.Logger,
deadline, ok := ctx.Deadline()
if !ok {
logger.Error("Context timeout not set for long poll API.",
tag.WorkflowHandlerName(handlerName), tag.Error(err))
return deadline, err
}
}
func GetPayloadsMapSize(data map[string]*commonpb.Payloads) int {
command_attr_validator.go ×4
size := 0
for key, payloads := range data {
size += len(key)
size += payloads.Size()
}
}
// CloneProto is a generic typed version of proto.Clone from proto.
return proto.Clone(v).(T)
}
if src == nil {
return nil
}
for k, v := range src {
result[k] = CloneProto(v)
}
return result
}
// DiscardUnknownProto discards unknown fields in a proto message.
return protorange.Range(m.ProtoReflect(), func(values protopath.Values) error {
m, ok := values.Index(-1).Value.Interface().(protoreflect.Message)
if ok && len(m.GetUnknown()) > 0 {
}
})
}
// MergeProtoExcludingFields merges fields from source into target, excluding specific fields.
// The fields to exclude are specified as pointers to fields in the target struct.
func MergeProtoExcludingFields(target, source proto.Message, doNotSyncFunc func(v any) []any) error {
util.go ×7
if target == nil || source == nil {
return serviceerror.NewInvalidArgument("target and source cannot be nil")
}
return serviceerror.NewInvalidArgument("target and source must be of the same type")
util.go ×1
}
excludeSet := make(map[string]struct{}, len(excludeFields))
for _, fieldPtr := range excludeFields {
fieldName, err := getFieldNameFromStruct(target, fieldPtr)
if err != nil {
return err
}
}
dstVal := reflect.ValueOf(target).Elem()
for i := 0; i < srcVal.NumField(); i++ {
field := srcVal.Type().Field(i)
if _, exclude := excludeSet[field.Name]; !exclude {
srcField := srcVal.Field(i)
dstField := dstVal.Field(i)
if dstField.CanSet() {
dstField.Set(srcField)
}
}
}
}
structVal := reflect.ValueOf(structPtr).Elem()
for i := 0; i < structVal.NumField(); i++ {
field := structVal.Field(i)
if field.CanSet() && field.Addr().Interface() == fieldPtr {
return structVal.Type().Field(i).Name, nil
}
}
return "", serviceerror.NewInternal("field not found in the struct")
}
// IsRetryableRPCError checks if the error is a retryable gRPC error.
var st *status.Status
stGetter, ok := err.(interface{ Status() *status.Status })
if ok {
st = stGetter.Status()
} else {
st, ok = status.FromError(err)
if !ok {
// Not a gRPC induced error
return false
}
}
// nolint:exhaustive
case codes.Canceled,
codes.Unknown,
codes.Unavailable,
codes.DeadlineExceeded,
codes.ResourceExhausted,
codes.Aborted,
return true
return false
}
}