From 4f7d8bb370e46259725d750b73a02dc48ae255a2 Mon Sep 17 00:00:00 2001 From: Carly de Frondeville Date: Sun, 30 Nov 2025 23:27:06 -0800 Subject: [PATCH 01/19] proto --- go.mod | 2 +- go.sum | 4 ++-- service/history/workflow/util.go | 25 ++++++++++++++++++++++++- 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index e900671cc1..1d096cab78 100644 --- a/go.mod +++ b/go.mod @@ -58,7 +58,7 @@ require ( go.opentelemetry.io/otel/sdk v1.34.0 go.opentelemetry.io/otel/sdk/metric v1.34.0 go.opentelemetry.io/otel/trace v1.34.0 - go.temporal.io/api v1.58.1-0.20251120183209-4485ca21da0e + go.temporal.io/api v1.58.1-0.20251201035831-15eee82a3d3f go.temporal.io/sdk v1.35.0 go.uber.org/fx v1.24.0 go.uber.org/mock v0.6.0 diff --git a/go.sum b/go.sum index 2baf8e3bea..b4574de92a 100644 --- a/go.sum +++ b/go.sum @@ -390,8 +390,8 @@ go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= -go.temporal.io/api v1.58.1-0.20251120183209-4485ca21da0e h1:Nuz7hXaulU/jyeeqw/igwxgQIxuhcrE5xSb8f0qooHY= -go.temporal.io/api v1.58.1-0.20251120183209-4485ca21da0e/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= +go.temporal.io/api v1.58.1-0.20251201035831-15eee82a3d3f h1:UwgqC0xTt7Zvsz8d+6O2DZOkhPvoMLKu8A0iPdHueLs= +go.temporal.io/api v1.58.1-0.20251201035831-15eee82a3d3f/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= go.temporal.io/sdk v1.35.0 h1:lRNAQ5As9rLgYa7HBvnmKyzxLcdElTuoFJ0FXM/AsLQ= go.temporal.io/sdk v1.35.0/go.mod h1:1q5MuLc2MEJ4lneZTHJzpVebW2oZnyxoIOWX3oFVebw= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= diff --git a/service/history/workflow/util.go b/service/history/workflow/util.go index d87470ee60..de36899374 100644 --- a/service/history/workflow/util.go +++ b/service/history/workflow/util.go @@ -249,7 +249,30 @@ func GetEffectiveVersioningBehavior(versioningInfo *workflowpb.WorkflowExecution if override.GetAutoUpgrade() { return enumspb.VERSIONING_BEHAVIOR_AUTO_UPGRADE } - return enumspb.VERSIONING_BEHAVIOR_PINNED + switch override.GetPinned().GetBehavior() { + case workflowpb.VersioningOverride_PINNED_OVERRIDE_BEHAVIOR_PINNED, workflowpb.VersioningOverride_PINNED_OVERRIDE_BEHAVIOR_UNSPECIFIED: + return enumspb.VERSIONING_BEHAVIOR_PINNED + case workflowpb.VersioningOverride_PINNED_OVERRIDE_BEHAVIOR_PINNED_UNTIL_CONTINUE_AS_NEW: + return enumspb.VERSIONING_BEHAVIOR_PINNED_UNTIL_CONTINUE_AS_NEW + case workflowpb.VersioningOverride_PINNED_OVERRIDE_BEHAVIOR_KEEP_IF_PINNING: + if versioningInfo.GetBehavior() == enumspb.VERSIONING_BEHAVIOR_PINNED || + versioningInfo.GetBehavior() == enumspb.VERSIONING_BEHAVIOR_PINNED_UNTIL_CONTINUE_AS_NEW { + return versioningInfo.GetBehavior() + } else { + switch override.GetPinned().GetIfNotPinning() { + case workflowpb.VersioningOverride_NON_PINNING_POLICY_REJECT, workflowpb.VersioningOverride_NON_PINNING_POLICY_UNSPECIFIED: + // this should never happen, but if it does, treat it as ignore + //TODO(carlydf): do an antithesis-compatible assertion here + return versioningInfo.GetBehavior() + case workflowpb.VersioningOverride_NON_PINNING_POLICY_IGNORE: + return versioningInfo.GetBehavior() + case workflowpb.VersioningOverride_NON_PINNING_POLICY_PINNED: + return enumspb.VERSIONING_BEHAVIOR_PINNED + case workflowpb.VersioningOverride_NON_PINNING_POLICY_PINNED_UNTIL_CONTINUE_AS_NEW: + return enumspb.VERSIONING_BEHAVIOR_PINNED_UNTIL_CONTINUE_AS_NEW + } + } + } } return override.GetBehavior() // //nolint:staticcheck // SA1019: worker versioning v0.31 and v0.30 } From d2533839f9e5c485fb5c8b58b214a415ad614b63 Mon Sep 17 00:00:00 2001 From: Carly de Frondeville Date: Mon, 1 Dec 2025 00:18:43 -0800 Subject: [PATCH 02/19] update CaN, retry, cron, and child inheritance semantics to account for PINNED_UNTIL_CONTINUE_AS_NEW --- common/worker_versioning/worker_versioning.go | 10 +++++++++- .../transfer_queue_active_task_executor.go | 10 +++++++--- .../history/workflow/mutable_state_impl.go | 19 ++++++++++++++----- service/history/workflow/retry.go | 16 ++++++++++------ 4 files changed, 40 insertions(+), 15 deletions(-) diff --git a/common/worker_versioning/worker_versioning.go b/common/worker_versioning/worker_versioning.go index e64d8a44f8..def27a9bc1 100644 --- a/common/worker_versioning/worker_versioning.go +++ b/common/worker_versioning/worker_versioning.go @@ -466,10 +466,18 @@ func ValidateDeploymentVersionStringV31(version string) (*deploymentspb.WorkerDe return v, nil } +// OverrideIsPinned is true if the override behavior is any of the Pinned override behaviors. func OverrideIsPinned(override *workflowpb.VersioningOverride) bool { //nolint:staticcheck // SA1019: worker versioning v0.31 and v0.30 return override.GetBehavior() == enumspb.VERSIONING_BEHAVIOR_PINNED || - override.GetPinned().GetBehavior() == workflowpb.VersioningOverride_PINNED_OVERRIDE_BEHAVIOR_PINNED + override.GetPinned().GetBehavior() == workflowpb.VersioningOverride_PINNED_OVERRIDE_BEHAVIOR_UNSPECIFIED +} + +func BehaviorIsPinning(behavior enumspb.VersioningBehavior) bool { + if behavior == enumspb.VERSIONING_BEHAVIOR_PINNED || behavior == enumspb.VERSIONING_BEHAVIOR_PINNED_UNTIL_CONTINUE_AS_NEW { + return true + } + return false } func GetOverridePinnedVersion(override *workflowpb.VersioningOverride) *deploymentpb.WorkerDeploymentVersion { diff --git a/service/history/transfer_queue_active_task_executor.go b/service/history/transfer_queue_active_task_executor.go index 03ac48748a..f1167db7c8 100644 --- a/service/history/transfer_queue_active_task_executor.go +++ b/service/history/transfer_queue_active_task_executor.go @@ -914,9 +914,10 @@ func (t *transferQueueActiveTaskExecutor) processStartChildExecution( // the first matching task-queue-in-version check. newTQInPinnedVersion := false - // Child of pinned parent will inherit the parent's version if the Child's Task Queue belongs to that version. + // Child of a parent with a Pinning Behavior (Pinned or PinnedUntilContinueAsNew) will inherit the parent's version + // if the Child's Task Queue belongs to that version. var inheritedPinnedVersion *deploymentpb.WorkerDeploymentVersion - if mutableState.GetEffectiveVersioningBehavior() == enumspb.VERSIONING_BEHAVIOR_PINNED { + if worker_versioning.BehaviorIsPinning(mutableState.GetEffectiveVersioningBehavior()) { inheritedPinnedVersion = worker_versioning.ExternalWorkerDeploymentVersionFromDeployment(mutableState.GetEffectiveDeployment()) newTQ := attributes.GetTaskQueue().GetName() if attributes.GetNamespaceId() != mutableState.GetExecutionInfo().GetNamespaceId() { // don't inherit pinned version if child is in a different namespace @@ -933,8 +934,11 @@ func (t *transferQueueActiveTaskExecutor) processStartChildExecution( } // Pinned override is inherited if Task Queue of new run is compatible with the override version. + // Effective versioning behavior of the workflow must be a Pinning Behavior (Pinned or PinnedUntilContinueAsNew) + // to inherit the pinned override version. var inheritedPinnedOverride *workflowpb.VersioningOverride - if o := mutableState.GetExecutionInfo().GetVersioningInfo().GetVersioningOverride(); worker_versioning.OverrideIsPinned(o) { + if o := mutableState.GetExecutionInfo().GetVersioningInfo().GetVersioningOverride(); worker_versioning.OverrideIsPinned(o) && + worker_versioning.BehaviorIsPinning(workflow.GetEffectiveVersioningBehavior(mutableState.GetExecutionInfo().GetVersioningInfo())) { inheritedPinnedOverride = o newTQ := attributes.GetTaskQueue().GetName() if newTQ != mutableState.GetExecutionInfo().GetTaskQueue() && !newTQInPinnedVersion || diff --git a/service/history/workflow/mutable_state_impl.go b/service/history/workflow/mutable_state_impl.go index 267b71cb21..a70f8df852 100644 --- a/service/history/workflow/mutable_state_impl.go +++ b/service/history/workflow/mutable_state_impl.go @@ -2445,8 +2445,10 @@ func (ms *MutableStateImpl) addWorkflowExecutionStartedEventForContinueAsNew( // the first matching task-queue-in-version check. newTQInPinnedVersion := false - // New run initiated by workflow ContinueAsNew of pinned run, will inherit the previous run's version if the + // New run initiated by workflow ContinueAsNew of a Pinned run, will inherit the previous run's version if the // new run's Task Queue belongs to that version. + // If the initiating workflow is PinnedUntilContinueAsNew, the new run will start as AutoUpgrade in the first task + // and then assume the SDK-sent behavior on first workflow task completion. var inheritedPinnedVersion *deploymentpb.WorkerDeploymentVersion if previousExecutionState.GetEffectiveVersioningBehavior() == enumspb.VERSIONING_BEHAVIOR_PINNED { inheritedPinnedVersion = worker_versioning.ExternalWorkerDeploymentVersionFromDeployment(previousExecutionState.GetEffectiveDeployment()) @@ -2463,8 +2465,11 @@ func (ms *MutableStateImpl) addWorkflowExecutionStartedEventForContinueAsNew( } // Pinned override is inherited if Task Queue of new run is compatible with the override version. + // For continue-as-new, the effective versioning behavior of the workflow must be strictly Pinned (not + // PinnedUntilContinueAsNew) to inherit the pinned override version. var pinnedOverride *workflowpb.VersioningOverride - if o := previousExecutionInfo.GetVersioningInfo().GetVersioningOverride(); worker_versioning.OverrideIsPinned(o) { + if o := previousExecutionInfo.GetVersioningInfo().GetVersioningOverride(); worker_versioning.OverrideIsPinned(o) && + GetEffectiveVersioningBehavior(previousExecutionInfo.GetVersioningInfo()) == enumspb.VERSIONING_BEHAVIOR_PINNED { pinnedOverride = o newTQ := command.GetTaskQueue().GetName() if newTQ != previousExecutionInfo.GetTaskQueue() && !newTQInPinnedVersion { @@ -2472,11 +2477,13 @@ func (ms *MutableStateImpl) addWorkflowExecutionStartedEventForContinueAsNew( } } - // New run initiated by ContinueAsNew of an AUTO_UPGRADE workflow execution will inherit the previous run's - // deployment version and revision number iff the new run's Task Queue belongs to source deployment version. + // New run initiated by ContinueAsNew of an AUTO_UPGRADE or PINNED_UNTIL_CONTINUE_AS_NEW workflow execution will + // inherit the previous run's deployment version and revision number iff the new run's Task Queue belongs to the + // source deployment version. var sourceDeploymentVersion *deploymentpb.WorkerDeploymentVersion var sourceDeploymentRevisionNumber int64 - if previousExecutionState.GetEffectiveVersioningBehavior() == enumspb.VERSIONING_BEHAVIOR_AUTO_UPGRADE { + if previousExecutionState.GetEffectiveVersioningBehavior() == enumspb.VERSIONING_BEHAVIOR_AUTO_UPGRADE || + previousExecutionState.GetEffectiveVersioningBehavior() == enumspb.VERSIONING_BEHAVIOR_PINNED_UNTIL_CONTINUE_AS_NEW { sourceDeploymentVersion = worker_versioning.ExternalWorkerDeploymentVersionFromDeployment(previousExecutionState.GetEffectiveDeployment()) sourceDeploymentRevisionNumber = previousExecutionState.GetVersioningRevisionNumber() @@ -2868,6 +2875,7 @@ func (ms *MutableStateImpl) ApplyWorkflowExecutionStartedEvent( ms.executionInfo.VersioningInfo = &workflowpb.WorkflowExecutionVersioningInfo{} } ms.executionInfo.VersioningInfo.DeploymentVersion = event.GetInheritedPinnedVersion() + // TODO(carlydf): Can't just assume this anymore -- the behavior might need to be PINNED_UNTIL_CONTINUE_AS_NEW, could add a new field called InheritedPinningBehavior. ms.executionInfo.VersioningInfo.Behavior = enumspb.VERSIONING_BEHAVIOR_PINNED } @@ -2909,6 +2917,7 @@ func (ms *MutableStateImpl) ApplyWorkflowExecutionStartedEvent( ms.executionInfo.VersioningInfo = &workflowpb.WorkflowExecutionVersioningInfo{} } ms.executionInfo.VersioningInfo.DeploymentVersion = event.GetInheritedAutoUpgradeInfo().GetSourceDeploymentVersion() + // TODO(carlydf): Decide whether it's ok for continued-as-new workflows whose previous workflow had PINNED_UNTIL_CONTINUE_AS_NEW behavior to start with AUTO_UPGRADE behavior, or if they should instead start with blank or PINNED_UNTIL_CONTINUE_AS_NEW behavior. // Assume AutoUpgrade behavior for the first workflow task. ms.executionInfo.VersioningInfo.Behavior = enumspb.VERSIONING_BEHAVIOR_AUTO_UPGRADE } diff --git a/service/history/workflow/retry.go b/service/history/workflow/retry.go index aa3fa9527b..0870393204 100644 --- a/service/history/workflow/retry.go +++ b/service/history/workflow/retry.go @@ -243,17 +243,21 @@ func SetupNewWorkflowForRetryOrCron( } } + // Retries and Cron workflows initiated by workflows with a Pinning Behavior (Pinned or PinnedUntilContinueAsNew) will inherit + // the initiator's version if the resulting workflow's Task Queue belongs to that version. var pinnedOverride *workflowpb.VersioningOverride - if o := previousExecutionInfo.GetVersioningInfo().GetVersioningOverride(); worker_versioning.OverrideIsPinned(o) { + if o := previousExecutionInfo.GetVersioningInfo().GetVersioningOverride(); worker_versioning.OverrideIsPinned(o) && + worker_versioning.BehaviorIsPinning(GetEffectiveVersioningBehavior(previousExecutionInfo.GetVersioningInfo())) { pinnedOverride = o // retries and crons always go to the same task queue, so no need to check if override version is in new task queue } - // New run initiated by workflow Cron will never inherit. + // New run initiated by workflow Cron will never inherit a non-override version. // - // New run initiated by workflow Retry will only inherit if the retried run is effectively pinned at the time - // of retry, and the retried run inherited a pinned version when it started (ie. it is a child of a pinned - // parent, or a CaN of a pinned run, and is running on a Task Queue in the inherited version). + // New run initiated by workflow Retry will only inherit if the retried run is effectively Pinned or PinnedUntilContinueAsNew + // at the time of retry, and the retried run inherited a pinned version when it started (ie. it is a child of a parent + // with Pinned or PinnedUntilContinueAsNew behavior, or a CaN of run with Pinned behavior, and is running on a Task + // Queue in the inherited version). var inheritedPinnedVersion *deploymentpb.WorkerDeploymentVersion // If the previous run had an AutoUpgrade behavior, we pass down the source deployment version and revision number to the new run. // Note: We only pass down one of inheritedPinnedVersion or inheritedAutoUpgradeInfo, but not both! @@ -262,7 +266,7 @@ func SetupNewWorkflowForRetryOrCron( if initiator == enumspb.CONTINUE_AS_NEW_INITIATOR_RETRY { // retries and crons always go to the same task queue, so no need to check if override version is in new task queue - if GetEffectiveVersioningBehavior(previousExecutionInfo.GetVersioningInfo()) == enumspb.VERSIONING_BEHAVIOR_PINNED && + if worker_versioning.BehaviorIsPinning(GetEffectiveVersioningBehavior(previousExecutionInfo.GetVersioningInfo())) && startAttr.GetInheritedPinnedVersion() != nil { inheritedPinnedVersion = worker_versioning.ExternalWorkerDeploymentVersionFromDeployment(GetEffectiveDeployment(previousExecutionInfo.GetVersioningInfo())) } else if GetEffectiveVersioningBehavior(previousExecutionInfo.GetVersioningInfo()) == enumspb.VERSIONING_BEHAVIOR_AUTO_UPGRADE { From 91fa851bbd8d46f1942ada7636c36bbaf080082e Mon Sep 17 00:00:00 2001 From: Carly de Frondeville Date: Mon, 1 Dec 2025 01:06:03 -0800 Subject: [PATCH 03/19] error appropriately in update-options --- common/worker_versioning/worker_versioning.go | 23 ++++++++------ .../history/api/updateworkflowoptions/api.go | 30 +++++++++++++++++++ 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/common/worker_versioning/worker_versioning.go b/common/worker_versioning/worker_versioning.go index def27a9bc1..6c223c8387 100644 --- a/common/worker_versioning/worker_versioning.go +++ b/common/worker_versioning/worker_versioning.go @@ -510,12 +510,17 @@ func ValidateVersioningOverride(override *workflowpb.VersioningOverride) error { if override.GetAutoUpgrade() { // v0.32 return nil } else if p := override.GetPinned(); p != nil { - if p.GetVersion() == nil { - return serviceerror.NewInvalidArgument("must provide version if override is pinned.") + switch p.GetBehavior() { + case workflowpb.VersioningOverride_PINNED_OVERRIDE_BEHAVIOR_UNSPECIFIED: + return serviceerror.NewInvalidArgument("must specify pinned override behavior if override is pinned.") + case workflowpb.VersioningOverride_PINNED_OVERRIDE_BEHAVIOR_KEEP_IF_PINNING: + if p.GetIfNotPinning() == workflowpb.VersioningOverride_NON_PINNING_POLICY_UNSPECIFIED { + return serviceerror.NewInvalidArgument("must specify non-pinning policy if behavior is 'KEEP_IF_PINNING'") + } } if p.GetBehavior() == workflowpb.VersioningOverride_PINNED_OVERRIDE_BEHAVIOR_UNSPECIFIED { - return serviceerror.NewInvalidArgument("must specify pinned override behavior if override is pinned.") } + return nil } @@ -664,13 +669,13 @@ func calcRampThreshold(id string) float64 { //revive:disable-next-line:cognitive-complexity,confusing-results,function-result-limit,cyclomatic func CalculateTaskQueueVersioningInfo(deployments *persistencespb.DeploymentData) ( *deploymentspb.WorkerDeploymentVersion, // current version - int64, // current revision number - time.Time, // current update time + int64, // current revision number + time.Time, // current update time *deploymentspb.WorkerDeploymentVersion, // ramping version - bool, // is ramping (ramping_since_time != nil) - float32, // ramp percentage - int64, // ramping revision number - time.Time, // ramping update time + bool, // is ramping (ramping_since_time != nil) + float32, // ramp percentage + int64, // ramping revision number + time.Time, // ramping update time ) { if deployments == nil { return nil, 0, time.Time{}, nil, false, 0, 0, time.Time{} diff --git a/service/history/api/updateworkflowoptions/api.go b/service/history/api/updateworkflowoptions/api.go index 1f591bde30..0cbf47cbd6 100644 --- a/service/history/api/updateworkflowoptions/api.go +++ b/service/history/api/updateworkflowoptions/api.go @@ -13,6 +13,7 @@ import ( "go.temporal.io/server/service/history/api" "go.temporal.io/server/service/history/consts" historyi "go.temporal.io/server/service/history/interfaces" + "go.temporal.io/server/service/history/workflow" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/fieldmaskpb" ) @@ -106,6 +107,35 @@ func MergeAndApply( return mergedOpts, false, nil } + // Reject 'keep_if_pinning' override if base behavior is not a Pinning Behavior (Pinned or PinnedUntilContinueAsNew) + if mergedOpts.GetVersioningOverride().GetPinned().GetBehavior() == workflowpb.VersioningOverride_PINNED_OVERRIDE_BEHAVIOR_KEEP_IF_PINNING && + !worker_versioning.BehaviorIsPinning(ms.GetExecutionInfo().GetVersioningInfo().GetBehavior()) { + nonPinningPolicy := mergedOpts.GetVersioningOverride().GetPinned().GetIfNotPinning() + if nonPinningPolicy == workflowpb.VersioningOverride_NON_PINNING_POLICY_REJECT { + return nil, + false, + serviceerror.NewFailedPreconditionf("rejecting 'keep_if_pinning' override because 'non_pinning_policy' is 'REJECT' and behavior for workflow with id %v is '%s'", + ms.GetExecutionInfo().GetWorkflowId(), + ms.GetExecutionInfo().GetVersioningInfo().GetBehavior().String(), + ) + } + } + + // If the requested override is pinned and omitted optional pinned version, fill in the current pinned version if it exists, + // or error if no pinned version exists. + if mergedOpts.GetVersioningOverride().GetPinned().GetBehavior() != workflowpb.VersioningOverride_PINNED_OVERRIDE_BEHAVIOR_UNSPECIFIED && + mergedOpts.GetVersioningOverride().GetPinned().GetVersion() == nil { + currentVersion := worker_versioning.ExternalWorkerDeploymentVersionFromDeployment(workflow.GetEffectiveDeployment(ms.GetExecutionInfo().GetVersioningInfo())) + if worker_versioning.BehaviorIsPinning(workflow.GetEffectiveVersioningBehavior(ms.GetExecutionInfo().GetVersioningInfo())) { + mergedOpts.GetVersioningOverride().GetPinned().Version = currentVersion + } else { + return nil, false, serviceerror.NewFailedPreconditionf("must specify a specific pinned override version because workflow with id %v has behavior %s and is not yet pinned to any version", + ms.GetExecutionInfo().GetWorkflowId(), + ms.GetExecutionInfo().GetVersioningInfo().GetBehavior().String(), + ) + } + } + unsetOverride := false if mergedOpts.GetVersioningOverride() == nil { unsetOverride = true From 53c4d738648911305f5ab36fe16e53ec947d50cf Mon Sep 17 00:00:00 2001 From: Carly de Frondeville Date: Mon, 1 Dec 2025 01:11:54 -0800 Subject: [PATCH 04/19] reject pinned overrides without versions on workflow start --- service/frontend/workflow_handler.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/service/frontend/workflow_handler.go b/service/frontend/workflow_handler.go index 47a5425b24..20dd6a3cbd 100644 --- a/service/frontend/workflow_handler.go +++ b/service/frontend/workflow_handler.go @@ -526,6 +526,14 @@ func (wh *WorkflowHandler) prepareStartWorkflowRequest( return nil, err } + if err := worker_versioning.ValidateVersioningOverride(request.GetVersioningOverride()); err != nil { + return nil, err + } + if request.GetVersioningOverride().GetPinned().GetBehavior() != workflowpb.VersioningOverride_PINNED_OVERRIDE_BEHAVIOR_UNSPECIFIED && + request.GetVersioningOverride().GetPinned().GetVersion() == nil { + return nil, serviceerror.NewInvalidArgument("override version is required to start a workflow with a pinned override") + } + return request, nil } @@ -2074,6 +2082,14 @@ func (wh *WorkflowHandler) SignalWithStartWorkflowExecution(ctx context.Context, return nil, err } + if err := worker_versioning.ValidateVersioningOverride(request.GetVersioningOverride()); err != nil { + return nil, err + } + if request.GetVersioningOverride().GetPinned().GetBehavior() != workflowpb.VersioningOverride_PINNED_OVERRIDE_BEHAVIOR_UNSPECIFIED && + request.GetVersioningOverride().GetPinned().GetVersion() == nil { + return nil, serviceerror.NewInvalidArgument("override version is required to start a workflow with a pinned override") + } + namespaceID, err := wh.namespaceRegistry.GetNamespaceID(namespaceName) if err != nil { return nil, err From 5607965af465199fd2f0e4cd2aee2b643b3695d1 Mon Sep 17 00:00:00 2001 From: Carly de Frondeville Date: Mon, 1 Dec 2025 02:00:03 -0800 Subject: [PATCH 05/19] send continue-as-new suggestion based on target version --- api/historyservice/v1/request_response.pb.go | 436 +++++++++--------- common/worker_versioning/worker_versioning.go | 12 +- .../historyservice/v1/request_response.proto | 3 + service/history/api/create_workflow_util.go | 1 + .../api/recordworkflowtaskstarted/api.go | 1 + .../api/respondworkflowtaskcompleted/api.go | 2 + .../respondworkflowtaskcompleted/api_test.go | 1 + service/history/history_engine_test.go | 1 + service/history/interfaces/mutable_state.go | 2 +- service/history/ndc/workflow_resetter.go | 1 + .../history/workflow/mutable_state_impl.go | 3 +- .../workflow/workflow_task_state_machine.go | 9 + service/matching/matching_engine.go | 2 + service/matching/task.go | 15 +- .../matching/task_queue_partition_manager.go | 88 ++-- 15 files changed, 317 insertions(+), 260 deletions(-) diff --git a/api/historyservice/v1/request_response.pb.go b/api/historyservice/v1/request_response.pb.go index 1f573bad57..c0cf24a8a7 100644 --- a/api/historyservice/v1/request_response.pb.go +++ b/api/historyservice/v1/request_response.pb.go @@ -1151,8 +1151,11 @@ type RecordWorkflowTaskStartedRequest struct { // Revision number that was sent by matching when the task was dispatched. Used to resolve eventual consistency issues // that may arise due to stale routing configs in task queue partitions. TaskDispatchRevisionNumber int64 `protobuf:"varint,12,opt,name=task_dispatch_revision_number,json=taskDispatchRevisionNumber,proto3" json:"task_dispatch_revision_number,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Target worker deployment version according to matching when starting the task. + // Computed after matching with a poller, right before calling RecordWorkflowTaskStarted. + TargetDeploymentVersion *v16.WorkerDeploymentVersion `protobuf:"bytes,13,opt,name=target_deployment_version,json=targetDeploymentVersion,proto3" json:"target_deployment_version,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *RecordWorkflowTaskStartedRequest) Reset() { @@ -1262,6 +1265,13 @@ func (x *RecordWorkflowTaskStartedRequest) GetTaskDispatchRevisionNumber() int64 return 0 } +func (x *RecordWorkflowTaskStartedRequest) GetTargetDeploymentVersion() *v16.WorkerDeploymentVersion { + if x != nil { + return x.TargetDeploymentVersion + } + return nil +} + type RecordWorkflowTaskStartedResponse struct { state protoimpl.MessageState `protogen:"open.v1"` WorkflowType *v14.WorkflowType `protobuf:"bytes,1,opt,name=workflow_type,json=workflowType,proto3" json:"workflow_type,omitempty"` @@ -10199,7 +10209,7 @@ const file_temporal_server_api_historyservice_v1_request_response_proto_rawDesc "\x0estart_workflow\x18\x01 \x01(\v2E.temporal.server.api.historyservice.v1.StartWorkflowExecutionResponseH\x00R\rstartWorkflow\x12q\n" + "\x0fupdate_workflow\x18\x02 \x01(\v2F.temporal.server.api.historyservice.v1.UpdateWorkflowExecutionResponseH\x00R\x0eupdateWorkflowB\n" + "\n" + - "\bresponse\"\xc0\x06\n" + + "\bresponse\"\xb1\a\n" + " RecordWorkflowTaskStartedRequest\x12!\n" + "\fnamespace_id\x18\x01 \x01(\tR\vnamespaceId\x12X\n" + "\x12workflow_execution\x18\x02 \x01(\v2).temporal.api.common.v1.WorkflowExecutionR\x11workflowExecution\x12,\n" + @@ -10213,7 +10223,8 @@ const file_temporal_server_api_historyservice_v1_request_response_proto_rawDesc "\x11version_directive\x18\n" + " \x01(\v26.temporal.server.api.taskqueue.v1.TaskVersionDirectiveR\x10versionDirective\x12\x14\n" + "\x05stamp\x18\v \x01(\x05R\x05stamp\x12A\n" + - "\x1dtask_dispatch_revision_number\x18\f \x01(\x03R\x1ataskDispatchRevisionNumber:$\x92\xc4\x03 *\x1eworkflow_execution.workflow_idJ\x04\b\x04\x10\x05\"\x94\n" + + "\x1dtask_dispatch_revision_number\x18\f \x01(\x03R\x1ataskDispatchRevisionNumber\x12o\n" + + "\x19target_deployment_version\x18\r \x01(\v23.temporal.api.deployment.v1.WorkerDeploymentVersionR\x17targetDeploymentVersion:$\x92\xc4\x03 *\x1eworkflow_execution.workflow_idJ\x04\b\x04\x10\x05\"\x94\n" + "\n" + "!RecordWorkflowTaskStartedResponse\x12I\n" + "\rworkflow_type\x18\x01 \x01(\v2$.temporal.api.common.v1.WorkflowTypeR\fworkflowType\x129\n" + @@ -11156,214 +11167,215 @@ var file_temporal_server_api_historyservice_v1_request_response_proto_depIdxs = 190, // 46: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedRequest.build_id_redirect_info:type_name -> temporal.server.api.taskqueue.v1.BuildIdRedirectInfo 191, // 47: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedRequest.scheduled_deployment:type_name -> temporal.api.deployment.v1.Deployment 192, // 48: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedRequest.version_directive:type_name -> temporal.server.api.taskqueue.v1.TaskVersionDirective - 184, // 49: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.workflow_type:type_name -> temporal.api.common.v1.WorkflowType - 193, // 50: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.transient_workflow_task:type_name -> temporal.server.api.history.v1.TransientWorkflowTaskInfo - 185, // 51: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.workflow_execution_task_queue:type_name -> temporal.api.taskqueue.v1.TaskQueue - 167, // 52: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.scheduled_time:type_name -> google.protobuf.Timestamp - 167, // 53: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.started_time:type_name -> google.protobuf.Timestamp - 158, // 54: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.queries:type_name -> temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.QueriesEntry - 177, // 55: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.clock:type_name -> temporal.server.api.clock.v1.VectorClock - 194, // 56: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.messages:type_name -> temporal.api.protocol.v1.Message - 195, // 57: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.history:type_name -> temporal.api.history.v1.History - 195, // 58: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.raw_history:type_name -> temporal.api.history.v1.History - 184, // 59: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.workflow_type:type_name -> temporal.api.common.v1.WorkflowType - 193, // 60: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.transient_workflow_task:type_name -> temporal.server.api.history.v1.TransientWorkflowTaskInfo - 185, // 61: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.workflow_execution_task_queue:type_name -> temporal.api.taskqueue.v1.TaskQueue - 167, // 62: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.scheduled_time:type_name -> google.protobuf.Timestamp - 167, // 63: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.started_time:type_name -> google.protobuf.Timestamp - 159, // 64: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.queries:type_name -> temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.QueriesEntry - 177, // 65: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.clock:type_name -> temporal.server.api.clock.v1.VectorClock - 194, // 66: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.messages:type_name -> temporal.api.protocol.v1.Message - 195, // 67: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.history:type_name -> temporal.api.history.v1.History - 181, // 68: temporal.server.api.historyservice.v1.RecordActivityTaskStartedRequest.workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution - 196, // 69: temporal.server.api.historyservice.v1.RecordActivityTaskStartedRequest.poll_request:type_name -> temporal.api.workflowservice.v1.PollActivityTaskQueueRequest - 177, // 70: temporal.server.api.historyservice.v1.RecordActivityTaskStartedRequest.clock:type_name -> temporal.server.api.clock.v1.VectorClock - 190, // 71: temporal.server.api.historyservice.v1.RecordActivityTaskStartedRequest.build_id_redirect_info:type_name -> temporal.server.api.taskqueue.v1.BuildIdRedirectInfo - 191, // 72: temporal.server.api.historyservice.v1.RecordActivityTaskStartedRequest.scheduled_deployment:type_name -> temporal.api.deployment.v1.Deployment - 192, // 73: temporal.server.api.historyservice.v1.RecordActivityTaskStartedRequest.version_directive:type_name -> temporal.server.api.taskqueue.v1.TaskVersionDirective - 197, // 74: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.scheduled_event:type_name -> temporal.api.history.v1.HistoryEvent - 167, // 75: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.started_time:type_name -> google.protobuf.Timestamp - 167, // 76: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.current_attempt_scheduled_time:type_name -> google.protobuf.Timestamp - 170, // 77: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.heartbeat_details:type_name -> temporal.api.common.v1.Payloads - 184, // 78: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.workflow_type:type_name -> temporal.api.common.v1.WorkflowType - 177, // 79: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.clock:type_name -> temporal.server.api.clock.v1.VectorClock - 198, // 80: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.priority:type_name -> temporal.api.common.v1.Priority - 199, // 81: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.retry_policy:type_name -> temporal.api.common.v1.RetryPolicy - 200, // 82: temporal.server.api.historyservice.v1.RespondWorkflowTaskCompletedRequest.complete_request:type_name -> temporal.api.workflowservice.v1.RespondWorkflowTaskCompletedRequest - 12, // 83: temporal.server.api.historyservice.v1.RespondWorkflowTaskCompletedResponse.started_response:type_name -> temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse - 201, // 84: temporal.server.api.historyservice.v1.RespondWorkflowTaskCompletedResponse.activity_tasks:type_name -> temporal.api.workflowservice.v1.PollActivityTaskQueueResponse - 178, // 85: temporal.server.api.historyservice.v1.RespondWorkflowTaskCompletedResponse.new_workflow_task:type_name -> temporal.api.workflowservice.v1.PollWorkflowTaskQueueResponse - 202, // 86: temporal.server.api.historyservice.v1.RespondWorkflowTaskFailedRequest.failed_request:type_name -> temporal.api.workflowservice.v1.RespondWorkflowTaskFailedRequest - 181, // 87: temporal.server.api.historyservice.v1.IsWorkflowTaskValidRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution - 177, // 88: temporal.server.api.historyservice.v1.IsWorkflowTaskValidRequest.clock:type_name -> temporal.server.api.clock.v1.VectorClock - 203, // 89: temporal.server.api.historyservice.v1.RecordActivityTaskHeartbeatRequest.heartbeat_request:type_name -> temporal.api.workflowservice.v1.RecordActivityTaskHeartbeatRequest - 204, // 90: temporal.server.api.historyservice.v1.RespondActivityTaskCompletedRequest.complete_request:type_name -> temporal.api.workflowservice.v1.RespondActivityTaskCompletedRequest - 205, // 91: temporal.server.api.historyservice.v1.RespondActivityTaskFailedRequest.failed_request:type_name -> temporal.api.workflowservice.v1.RespondActivityTaskFailedRequest - 206, // 92: temporal.server.api.historyservice.v1.RespondActivityTaskCanceledRequest.cancel_request:type_name -> temporal.api.workflowservice.v1.RespondActivityTaskCanceledRequest - 181, // 93: temporal.server.api.historyservice.v1.IsActivityTaskValidRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution - 177, // 94: temporal.server.api.historyservice.v1.IsActivityTaskValidRequest.clock:type_name -> temporal.server.api.clock.v1.VectorClock - 207, // 95: temporal.server.api.historyservice.v1.SignalWorkflowExecutionRequest.signal_request:type_name -> temporal.api.workflowservice.v1.SignalWorkflowExecutionRequest - 181, // 96: temporal.server.api.historyservice.v1.SignalWorkflowExecutionRequest.external_workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution - 208, // 97: temporal.server.api.historyservice.v1.SignalWithStartWorkflowExecutionRequest.signal_with_start_request:type_name -> temporal.api.workflowservice.v1.SignalWithStartWorkflowExecutionRequest - 181, // 98: temporal.server.api.historyservice.v1.RemoveSignalMutableStateRequest.workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution - 209, // 99: temporal.server.api.historyservice.v1.TerminateWorkflowExecutionRequest.terminate_request:type_name -> temporal.api.workflowservice.v1.TerminateWorkflowExecutionRequest - 181, // 100: temporal.server.api.historyservice.v1.TerminateWorkflowExecutionRequest.external_workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution - 181, // 101: temporal.server.api.historyservice.v1.DeleteWorkflowExecutionRequest.workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution - 210, // 102: temporal.server.api.historyservice.v1.ResetWorkflowExecutionRequest.reset_request:type_name -> temporal.api.workflowservice.v1.ResetWorkflowExecutionRequest - 211, // 103: temporal.server.api.historyservice.v1.RequestCancelWorkflowExecutionRequest.cancel_request:type_name -> temporal.api.workflowservice.v1.RequestCancelWorkflowExecutionRequest - 181, // 104: temporal.server.api.historyservice.v1.RequestCancelWorkflowExecutionRequest.external_workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution - 181, // 105: temporal.server.api.historyservice.v1.ScheduleWorkflowTaskRequest.workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution - 177, // 106: temporal.server.api.historyservice.v1.ScheduleWorkflowTaskRequest.child_clock:type_name -> temporal.server.api.clock.v1.VectorClock - 177, // 107: temporal.server.api.historyservice.v1.ScheduleWorkflowTaskRequest.parent_clock:type_name -> temporal.server.api.clock.v1.VectorClock - 181, // 108: temporal.server.api.historyservice.v1.VerifyFirstWorkflowTaskScheduledRequest.workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution - 177, // 109: temporal.server.api.historyservice.v1.VerifyFirstWorkflowTaskScheduledRequest.clock:type_name -> temporal.server.api.clock.v1.VectorClock - 181, // 110: temporal.server.api.historyservice.v1.RecordChildExecutionCompletedRequest.parent_execution:type_name -> temporal.api.common.v1.WorkflowExecution - 181, // 111: temporal.server.api.historyservice.v1.RecordChildExecutionCompletedRequest.child_execution:type_name -> temporal.api.common.v1.WorkflowExecution - 197, // 112: temporal.server.api.historyservice.v1.RecordChildExecutionCompletedRequest.completion_event:type_name -> temporal.api.history.v1.HistoryEvent - 177, // 113: temporal.server.api.historyservice.v1.RecordChildExecutionCompletedRequest.clock:type_name -> temporal.server.api.clock.v1.VectorClock - 181, // 114: temporal.server.api.historyservice.v1.VerifyChildExecutionCompletionRecordedRequest.parent_execution:type_name -> temporal.api.common.v1.WorkflowExecution - 181, // 115: temporal.server.api.historyservice.v1.VerifyChildExecutionCompletionRecordedRequest.child_execution:type_name -> temporal.api.common.v1.WorkflowExecution - 177, // 116: temporal.server.api.historyservice.v1.VerifyChildExecutionCompletionRecordedRequest.clock:type_name -> temporal.server.api.clock.v1.VectorClock - 212, // 117: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionRequest.request:type_name -> temporal.api.workflowservice.v1.DescribeWorkflowExecutionRequest - 213, // 118: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.execution_config:type_name -> temporal.api.workflow.v1.WorkflowExecutionConfig - 214, // 119: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.workflow_execution_info:type_name -> temporal.api.workflow.v1.WorkflowExecutionInfo - 215, // 120: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.pending_activities:type_name -> temporal.api.workflow.v1.PendingActivityInfo - 216, // 121: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.pending_children:type_name -> temporal.api.workflow.v1.PendingChildExecutionInfo - 217, // 122: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.pending_workflow_task:type_name -> temporal.api.workflow.v1.PendingWorkflowTaskInfo - 218, // 123: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.callbacks:type_name -> temporal.api.workflow.v1.CallbackInfo - 219, // 124: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.pending_nexus_operations:type_name -> temporal.api.workflow.v1.PendingNexusOperationInfo - 220, // 125: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.workflow_extended_info:type_name -> temporal.api.workflow.v1.WorkflowExecutionExtendedInfo - 181, // 126: temporal.server.api.historyservice.v1.ReplicateEventsV2Request.workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution - 182, // 127: temporal.server.api.historyservice.v1.ReplicateEventsV2Request.version_history_items:type_name -> temporal.server.api.history.v1.VersionHistoryItem - 221, // 128: temporal.server.api.historyservice.v1.ReplicateEventsV2Request.events:type_name -> temporal.api.common.v1.DataBlob - 221, // 129: temporal.server.api.historyservice.v1.ReplicateEventsV2Request.new_run_events:type_name -> temporal.api.common.v1.DataBlob - 222, // 130: temporal.server.api.historyservice.v1.ReplicateEventsV2Request.base_execution_info:type_name -> temporal.server.api.workflow.v1.BaseExecutionInfo - 223, // 131: temporal.server.api.historyservice.v1.ReplicateWorkflowStateRequest.workflow_state:type_name -> temporal.server.api.persistence.v1.WorkflowMutableState - 167, // 132: temporal.server.api.historyservice.v1.SyncShardStatusRequest.status_time:type_name -> google.protobuf.Timestamp - 167, // 133: temporal.server.api.historyservice.v1.SyncActivityRequest.scheduled_time:type_name -> google.protobuf.Timestamp - 167, // 134: temporal.server.api.historyservice.v1.SyncActivityRequest.started_time:type_name -> google.protobuf.Timestamp - 167, // 135: temporal.server.api.historyservice.v1.SyncActivityRequest.last_heartbeat_time:type_name -> google.protobuf.Timestamp - 170, // 136: temporal.server.api.historyservice.v1.SyncActivityRequest.details:type_name -> temporal.api.common.v1.Payloads - 169, // 137: temporal.server.api.historyservice.v1.SyncActivityRequest.last_failure:type_name -> temporal.api.failure.v1.Failure - 224, // 138: temporal.server.api.historyservice.v1.SyncActivityRequest.version_history:type_name -> temporal.server.api.history.v1.VersionHistory - 222, // 139: temporal.server.api.historyservice.v1.SyncActivityRequest.base_execution_info:type_name -> temporal.server.api.workflow.v1.BaseExecutionInfo - 167, // 140: temporal.server.api.historyservice.v1.SyncActivityRequest.first_scheduled_time:type_name -> google.protobuf.Timestamp - 167, // 141: temporal.server.api.historyservice.v1.SyncActivityRequest.last_attempt_complete_time:type_name -> google.protobuf.Timestamp - 171, // 142: temporal.server.api.historyservice.v1.SyncActivityRequest.retry_initial_interval:type_name -> google.protobuf.Duration - 171, // 143: temporal.server.api.historyservice.v1.SyncActivityRequest.retry_maximum_interval:type_name -> google.protobuf.Duration - 64, // 144: temporal.server.api.historyservice.v1.SyncActivitiesRequest.activities_info:type_name -> temporal.server.api.historyservice.v1.ActivitySyncInfo - 167, // 145: temporal.server.api.historyservice.v1.ActivitySyncInfo.scheduled_time:type_name -> google.protobuf.Timestamp - 167, // 146: temporal.server.api.historyservice.v1.ActivitySyncInfo.started_time:type_name -> google.protobuf.Timestamp - 167, // 147: temporal.server.api.historyservice.v1.ActivitySyncInfo.last_heartbeat_time:type_name -> google.protobuf.Timestamp - 170, // 148: temporal.server.api.historyservice.v1.ActivitySyncInfo.details:type_name -> temporal.api.common.v1.Payloads - 169, // 149: temporal.server.api.historyservice.v1.ActivitySyncInfo.last_failure:type_name -> temporal.api.failure.v1.Failure - 224, // 150: temporal.server.api.historyservice.v1.ActivitySyncInfo.version_history:type_name -> temporal.server.api.history.v1.VersionHistory - 167, // 151: temporal.server.api.historyservice.v1.ActivitySyncInfo.first_scheduled_time:type_name -> google.protobuf.Timestamp - 167, // 152: temporal.server.api.historyservice.v1.ActivitySyncInfo.last_attempt_complete_time:type_name -> google.protobuf.Timestamp - 171, // 153: temporal.server.api.historyservice.v1.ActivitySyncInfo.retry_initial_interval:type_name -> google.protobuf.Duration - 171, // 154: temporal.server.api.historyservice.v1.ActivitySyncInfo.retry_maximum_interval:type_name -> google.protobuf.Duration - 181, // 155: temporal.server.api.historyservice.v1.DescribeMutableStateRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution - 223, // 156: temporal.server.api.historyservice.v1.DescribeMutableStateResponse.cache_mutable_state:type_name -> temporal.server.api.persistence.v1.WorkflowMutableState - 223, // 157: temporal.server.api.historyservice.v1.DescribeMutableStateResponse.database_mutable_state:type_name -> temporal.server.api.persistence.v1.WorkflowMutableState - 181, // 158: temporal.server.api.historyservice.v1.DescribeHistoryHostRequest.workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution - 225, // 159: temporal.server.api.historyservice.v1.DescribeHistoryHostResponse.namespace_cache:type_name -> temporal.server.api.namespace.v1.NamespaceCacheInfo - 226, // 160: temporal.server.api.historyservice.v1.GetShardResponse.shard_info:type_name -> temporal.server.api.persistence.v1.ShardInfo - 167, // 161: temporal.server.api.historyservice.v1.RemoveTaskRequest.visibility_time:type_name -> google.protobuf.Timestamp - 227, // 162: temporal.server.api.historyservice.v1.GetReplicationMessagesRequest.tokens:type_name -> temporal.server.api.replication.v1.ReplicationToken - 160, // 163: temporal.server.api.historyservice.v1.GetReplicationMessagesResponse.shard_messages:type_name -> temporal.server.api.historyservice.v1.GetReplicationMessagesResponse.ShardMessagesEntry - 228, // 164: temporal.server.api.historyservice.v1.GetDLQReplicationMessagesRequest.task_infos:type_name -> temporal.server.api.replication.v1.ReplicationTaskInfo - 229, // 165: temporal.server.api.historyservice.v1.GetDLQReplicationMessagesResponse.replication_tasks:type_name -> temporal.server.api.replication.v1.ReplicationTask - 230, // 166: temporal.server.api.historyservice.v1.QueryWorkflowRequest.request:type_name -> temporal.api.workflowservice.v1.QueryWorkflowRequest - 231, // 167: temporal.server.api.historyservice.v1.QueryWorkflowResponse.response:type_name -> temporal.api.workflowservice.v1.QueryWorkflowResponse - 232, // 168: temporal.server.api.historyservice.v1.ReapplyEventsRequest.request:type_name -> temporal.server.api.adminservice.v1.ReapplyEventsRequest - 233, // 169: temporal.server.api.historyservice.v1.GetDLQMessagesRequest.type:type_name -> temporal.server.api.enums.v1.DeadLetterQueueType - 233, // 170: temporal.server.api.historyservice.v1.GetDLQMessagesResponse.type:type_name -> temporal.server.api.enums.v1.DeadLetterQueueType - 229, // 171: temporal.server.api.historyservice.v1.GetDLQMessagesResponse.replication_tasks:type_name -> temporal.server.api.replication.v1.ReplicationTask - 228, // 172: temporal.server.api.historyservice.v1.GetDLQMessagesResponse.replication_tasks_info:type_name -> temporal.server.api.replication.v1.ReplicationTaskInfo - 233, // 173: temporal.server.api.historyservice.v1.PurgeDLQMessagesRequest.type:type_name -> temporal.server.api.enums.v1.DeadLetterQueueType - 233, // 174: temporal.server.api.historyservice.v1.MergeDLQMessagesRequest.type:type_name -> temporal.server.api.enums.v1.DeadLetterQueueType - 234, // 175: temporal.server.api.historyservice.v1.RefreshWorkflowTasksRequest.request:type_name -> temporal.server.api.adminservice.v1.RefreshWorkflowTasksRequest - 181, // 176: temporal.server.api.historyservice.v1.GenerateLastHistoryReplicationTasksRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution - 96, // 177: temporal.server.api.historyservice.v1.GetReplicationStatusResponse.shards:type_name -> temporal.server.api.historyservice.v1.ShardReplicationStatus - 167, // 178: temporal.server.api.historyservice.v1.ShardReplicationStatus.shard_local_time:type_name -> google.protobuf.Timestamp - 161, // 179: temporal.server.api.historyservice.v1.ShardReplicationStatus.remote_clusters:type_name -> temporal.server.api.historyservice.v1.ShardReplicationStatus.RemoteClustersEntry - 162, // 180: temporal.server.api.historyservice.v1.ShardReplicationStatus.handover_namespaces:type_name -> temporal.server.api.historyservice.v1.ShardReplicationStatus.HandoverNamespacesEntry - 167, // 181: temporal.server.api.historyservice.v1.ShardReplicationStatus.max_replication_task_visibility_time:type_name -> google.protobuf.Timestamp - 167, // 182: temporal.server.api.historyservice.v1.ShardReplicationStatusPerCluster.acked_task_visibility_time:type_name -> google.protobuf.Timestamp - 181, // 183: temporal.server.api.historyservice.v1.RebuildMutableStateRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution - 181, // 184: temporal.server.api.historyservice.v1.ImportWorkflowExecutionRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution - 221, // 185: temporal.server.api.historyservice.v1.ImportWorkflowExecutionRequest.history_batches:type_name -> temporal.api.common.v1.DataBlob - 224, // 186: temporal.server.api.historyservice.v1.ImportWorkflowExecutionRequest.version_history:type_name -> temporal.server.api.history.v1.VersionHistory - 181, // 187: temporal.server.api.historyservice.v1.DeleteWorkflowVisibilityRecordRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution - 167, // 188: temporal.server.api.historyservice.v1.DeleteWorkflowVisibilityRecordRequest.workflow_start_time:type_name -> google.protobuf.Timestamp - 167, // 189: temporal.server.api.historyservice.v1.DeleteWorkflowVisibilityRecordRequest.workflow_close_time:type_name -> google.protobuf.Timestamp - 235, // 190: temporal.server.api.historyservice.v1.UpdateWorkflowExecutionRequest.request:type_name -> temporal.api.workflowservice.v1.UpdateWorkflowExecutionRequest - 236, // 191: temporal.server.api.historyservice.v1.UpdateWorkflowExecutionResponse.response:type_name -> temporal.api.workflowservice.v1.UpdateWorkflowExecutionResponse - 237, // 192: temporal.server.api.historyservice.v1.StreamWorkflowReplicationMessagesRequest.sync_replication_state:type_name -> temporal.server.api.replication.v1.SyncReplicationState - 238, // 193: temporal.server.api.historyservice.v1.StreamWorkflowReplicationMessagesResponse.messages:type_name -> temporal.server.api.replication.v1.WorkflowReplicationMessages - 239, // 194: temporal.server.api.historyservice.v1.PollWorkflowExecutionUpdateRequest.request:type_name -> temporal.api.workflowservice.v1.PollWorkflowExecutionUpdateRequest - 240, // 195: temporal.server.api.historyservice.v1.PollWorkflowExecutionUpdateResponse.response:type_name -> temporal.api.workflowservice.v1.PollWorkflowExecutionUpdateResponse - 241, // 196: temporal.server.api.historyservice.v1.GetWorkflowExecutionHistoryRequest.request:type_name -> temporal.api.workflowservice.v1.GetWorkflowExecutionHistoryRequest - 242, // 197: temporal.server.api.historyservice.v1.GetWorkflowExecutionHistoryResponse.response:type_name -> temporal.api.workflowservice.v1.GetWorkflowExecutionHistoryResponse - 195, // 198: temporal.server.api.historyservice.v1.GetWorkflowExecutionHistoryResponse.history:type_name -> temporal.api.history.v1.History - 242, // 199: temporal.server.api.historyservice.v1.GetWorkflowExecutionHistoryResponseWithRaw.response:type_name -> temporal.api.workflowservice.v1.GetWorkflowExecutionHistoryResponse - 243, // 200: temporal.server.api.historyservice.v1.GetWorkflowExecutionHistoryReverseRequest.request:type_name -> temporal.api.workflowservice.v1.GetWorkflowExecutionHistoryReverseRequest - 244, // 201: temporal.server.api.historyservice.v1.GetWorkflowExecutionHistoryReverseResponse.response:type_name -> temporal.api.workflowservice.v1.GetWorkflowExecutionHistoryReverseResponse - 245, // 202: temporal.server.api.historyservice.v1.GetWorkflowExecutionRawHistoryV2Request.request:type_name -> temporal.server.api.adminservice.v1.GetWorkflowExecutionRawHistoryV2Request - 246, // 203: temporal.server.api.historyservice.v1.GetWorkflowExecutionRawHistoryV2Response.response:type_name -> temporal.server.api.adminservice.v1.GetWorkflowExecutionRawHistoryV2Response - 247, // 204: temporal.server.api.historyservice.v1.GetWorkflowExecutionRawHistoryRequest.request:type_name -> temporal.server.api.adminservice.v1.GetWorkflowExecutionRawHistoryRequest - 248, // 205: temporal.server.api.historyservice.v1.GetWorkflowExecutionRawHistoryResponse.response:type_name -> temporal.server.api.adminservice.v1.GetWorkflowExecutionRawHistoryResponse - 249, // 206: temporal.server.api.historyservice.v1.ForceDeleteWorkflowExecutionRequest.request:type_name -> temporal.server.api.adminservice.v1.DeleteWorkflowExecutionRequest - 250, // 207: temporal.server.api.historyservice.v1.ForceDeleteWorkflowExecutionResponse.response:type_name -> temporal.server.api.adminservice.v1.DeleteWorkflowExecutionResponse - 251, // 208: temporal.server.api.historyservice.v1.GetDLQTasksRequest.dlq_key:type_name -> temporal.server.api.common.v1.HistoryDLQKey - 252, // 209: temporal.server.api.historyservice.v1.GetDLQTasksResponse.dlq_tasks:type_name -> temporal.server.api.common.v1.HistoryDLQTask - 251, // 210: temporal.server.api.historyservice.v1.DeleteDLQTasksRequest.dlq_key:type_name -> temporal.server.api.common.v1.HistoryDLQKey - 253, // 211: temporal.server.api.historyservice.v1.DeleteDLQTasksRequest.inclusive_max_task_metadata:type_name -> temporal.server.api.common.v1.HistoryDLQTaskMetadata - 163, // 212: temporal.server.api.historyservice.v1.ListQueuesResponse.queues:type_name -> temporal.server.api.historyservice.v1.ListQueuesResponse.QueueInfo - 164, // 213: temporal.server.api.historyservice.v1.AddTasksRequest.tasks:type_name -> temporal.server.api.historyservice.v1.AddTasksRequest.Task - 254, // 214: temporal.server.api.historyservice.v1.ListTasksRequest.request:type_name -> temporal.server.api.adminservice.v1.ListHistoryTasksRequest - 255, // 215: temporal.server.api.historyservice.v1.ListTasksResponse.response:type_name -> temporal.server.api.adminservice.v1.ListHistoryTasksResponse - 256, // 216: temporal.server.api.historyservice.v1.CompleteNexusOperationChasmRequest.completion:type_name -> temporal.server.api.token.v1.NexusOperationCompletion - 257, // 217: temporal.server.api.historyservice.v1.CompleteNexusOperationChasmRequest.success:type_name -> temporal.api.common.v1.Payload - 169, // 218: temporal.server.api.historyservice.v1.CompleteNexusOperationChasmRequest.failure:type_name -> temporal.api.failure.v1.Failure - 167, // 219: temporal.server.api.historyservice.v1.CompleteNexusOperationChasmRequest.close_time:type_name -> google.protobuf.Timestamp - 256, // 220: temporal.server.api.historyservice.v1.CompleteNexusOperationRequest.completion:type_name -> temporal.server.api.token.v1.NexusOperationCompletion - 257, // 221: temporal.server.api.historyservice.v1.CompleteNexusOperationRequest.success:type_name -> temporal.api.common.v1.Payload - 258, // 222: temporal.server.api.historyservice.v1.CompleteNexusOperationRequest.failure:type_name -> temporal.api.nexus.v1.Failure - 167, // 223: temporal.server.api.historyservice.v1.CompleteNexusOperationRequest.start_time:type_name -> google.protobuf.Timestamp - 180, // 224: temporal.server.api.historyservice.v1.CompleteNexusOperationRequest.links:type_name -> temporal.api.common.v1.Link - 259, // 225: temporal.server.api.historyservice.v1.InvokeStateMachineMethodRequest.ref:type_name -> temporal.server.api.persistence.v1.StateMachineRef - 260, // 226: temporal.server.api.historyservice.v1.DeepHealthCheckResponse.state:type_name -> temporal.server.api.enums.v1.HealthState - 181, // 227: temporal.server.api.historyservice.v1.SyncWorkflowStateRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution - 183, // 228: temporal.server.api.historyservice.v1.SyncWorkflowStateRequest.versioned_transition:type_name -> temporal.server.api.persistence.v1.VersionedTransition - 187, // 229: temporal.server.api.historyservice.v1.SyncWorkflowStateRequest.version_histories:type_name -> temporal.server.api.history.v1.VersionHistories - 261, // 230: temporal.server.api.historyservice.v1.SyncWorkflowStateResponse.versioned_transition_artifact:type_name -> temporal.server.api.replication.v1.VersionedTransitionArtifact - 262, // 231: temporal.server.api.historyservice.v1.UpdateActivityOptionsRequest.update_request:type_name -> temporal.api.workflowservice.v1.UpdateActivityOptionsRequest - 263, // 232: temporal.server.api.historyservice.v1.UpdateActivityOptionsResponse.activity_options:type_name -> temporal.api.activity.v1.ActivityOptions - 264, // 233: temporal.server.api.historyservice.v1.PauseActivityRequest.frontend_request:type_name -> temporal.api.workflowservice.v1.PauseActivityRequest - 265, // 234: temporal.server.api.historyservice.v1.UnpauseActivityRequest.frontend_request:type_name -> temporal.api.workflowservice.v1.UnpauseActivityRequest - 266, // 235: temporal.server.api.historyservice.v1.ResetActivityRequest.frontend_request:type_name -> temporal.api.workflowservice.v1.ResetActivityRequest - 267, // 236: temporal.server.api.historyservice.v1.UpdateWorkflowExecutionOptionsRequest.update_request:type_name -> temporal.api.workflowservice.v1.UpdateWorkflowExecutionOptionsRequest - 268, // 237: temporal.server.api.historyservice.v1.UpdateWorkflowExecutionOptionsResponse.workflow_execution_options:type_name -> temporal.api.workflow.v1.WorkflowExecutionOptions - 269, // 238: temporal.server.api.historyservice.v1.PauseWorkflowExecutionRequest.pause_request:type_name -> temporal.api.workflowservice.v1.PauseWorkflowExecutionRequest - 270, // 239: temporal.server.api.historyservice.v1.UnpauseWorkflowExecutionRequest.unpause_request:type_name -> temporal.api.workflowservice.v1.UnpauseWorkflowExecutionRequest - 1, // 240: temporal.server.api.historyservice.v1.ExecuteMultiOperationRequest.Operation.start_workflow:type_name -> temporal.server.api.historyservice.v1.StartWorkflowExecutionRequest - 105, // 241: temporal.server.api.historyservice.v1.ExecuteMultiOperationRequest.Operation.update_workflow:type_name -> temporal.server.api.historyservice.v1.UpdateWorkflowExecutionRequest - 2, // 242: temporal.server.api.historyservice.v1.ExecuteMultiOperationResponse.Response.start_workflow:type_name -> temporal.server.api.historyservice.v1.StartWorkflowExecutionResponse - 106, // 243: temporal.server.api.historyservice.v1.ExecuteMultiOperationResponse.Response.update_workflow:type_name -> temporal.server.api.historyservice.v1.UpdateWorkflowExecutionResponse - 271, // 244: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.QueriesEntry.value:type_name -> temporal.api.query.v1.WorkflowQuery - 271, // 245: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.QueriesEntry.value:type_name -> temporal.api.query.v1.WorkflowQuery - 272, // 246: temporal.server.api.historyservice.v1.GetReplicationMessagesResponse.ShardMessagesEntry.value:type_name -> temporal.server.api.replication.v1.ReplicationMessages - 98, // 247: temporal.server.api.historyservice.v1.ShardReplicationStatus.RemoteClustersEntry.value:type_name -> temporal.server.api.historyservice.v1.ShardReplicationStatusPerCluster - 97, // 248: temporal.server.api.historyservice.v1.ShardReplicationStatus.HandoverNamespacesEntry.value:type_name -> temporal.server.api.historyservice.v1.HandoverNamespaceInfo - 221, // 249: temporal.server.api.historyservice.v1.AddTasksRequest.Task.blob:type_name -> temporal.api.common.v1.DataBlob - 273, // 250: temporal.server.api.historyservice.v1.routing:extendee -> google.protobuf.MessageOptions - 0, // 251: temporal.server.api.historyservice.v1.routing:type_name -> temporal.server.api.historyservice.v1.RoutingOptions - 252, // [252:252] is the sub-list for method output_type - 252, // [252:252] is the sub-list for method input_type - 251, // [251:252] is the sub-list for extension type_name - 250, // [250:251] is the sub-list for extension extendee - 0, // [0:250] is the sub-list for field type_name + 175, // 49: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedRequest.target_deployment_version:type_name -> temporal.api.deployment.v1.WorkerDeploymentVersion + 184, // 50: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.workflow_type:type_name -> temporal.api.common.v1.WorkflowType + 193, // 51: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.transient_workflow_task:type_name -> temporal.server.api.history.v1.TransientWorkflowTaskInfo + 185, // 52: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.workflow_execution_task_queue:type_name -> temporal.api.taskqueue.v1.TaskQueue + 167, // 53: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.scheduled_time:type_name -> google.protobuf.Timestamp + 167, // 54: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.started_time:type_name -> google.protobuf.Timestamp + 158, // 55: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.queries:type_name -> temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.QueriesEntry + 177, // 56: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.clock:type_name -> temporal.server.api.clock.v1.VectorClock + 194, // 57: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.messages:type_name -> temporal.api.protocol.v1.Message + 195, // 58: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.history:type_name -> temporal.api.history.v1.History + 195, // 59: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.raw_history:type_name -> temporal.api.history.v1.History + 184, // 60: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.workflow_type:type_name -> temporal.api.common.v1.WorkflowType + 193, // 61: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.transient_workflow_task:type_name -> temporal.server.api.history.v1.TransientWorkflowTaskInfo + 185, // 62: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.workflow_execution_task_queue:type_name -> temporal.api.taskqueue.v1.TaskQueue + 167, // 63: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.scheduled_time:type_name -> google.protobuf.Timestamp + 167, // 64: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.started_time:type_name -> google.protobuf.Timestamp + 159, // 65: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.queries:type_name -> temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.QueriesEntry + 177, // 66: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.clock:type_name -> temporal.server.api.clock.v1.VectorClock + 194, // 67: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.messages:type_name -> temporal.api.protocol.v1.Message + 195, // 68: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.history:type_name -> temporal.api.history.v1.History + 181, // 69: temporal.server.api.historyservice.v1.RecordActivityTaskStartedRequest.workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution + 196, // 70: temporal.server.api.historyservice.v1.RecordActivityTaskStartedRequest.poll_request:type_name -> temporal.api.workflowservice.v1.PollActivityTaskQueueRequest + 177, // 71: temporal.server.api.historyservice.v1.RecordActivityTaskStartedRequest.clock:type_name -> temporal.server.api.clock.v1.VectorClock + 190, // 72: temporal.server.api.historyservice.v1.RecordActivityTaskStartedRequest.build_id_redirect_info:type_name -> temporal.server.api.taskqueue.v1.BuildIdRedirectInfo + 191, // 73: temporal.server.api.historyservice.v1.RecordActivityTaskStartedRequest.scheduled_deployment:type_name -> temporal.api.deployment.v1.Deployment + 192, // 74: temporal.server.api.historyservice.v1.RecordActivityTaskStartedRequest.version_directive:type_name -> temporal.server.api.taskqueue.v1.TaskVersionDirective + 197, // 75: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.scheduled_event:type_name -> temporal.api.history.v1.HistoryEvent + 167, // 76: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.started_time:type_name -> google.protobuf.Timestamp + 167, // 77: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.current_attempt_scheduled_time:type_name -> google.protobuf.Timestamp + 170, // 78: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.heartbeat_details:type_name -> temporal.api.common.v1.Payloads + 184, // 79: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.workflow_type:type_name -> temporal.api.common.v1.WorkflowType + 177, // 80: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.clock:type_name -> temporal.server.api.clock.v1.VectorClock + 198, // 81: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.priority:type_name -> temporal.api.common.v1.Priority + 199, // 82: temporal.server.api.historyservice.v1.RecordActivityTaskStartedResponse.retry_policy:type_name -> temporal.api.common.v1.RetryPolicy + 200, // 83: temporal.server.api.historyservice.v1.RespondWorkflowTaskCompletedRequest.complete_request:type_name -> temporal.api.workflowservice.v1.RespondWorkflowTaskCompletedRequest + 12, // 84: temporal.server.api.historyservice.v1.RespondWorkflowTaskCompletedResponse.started_response:type_name -> temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse + 201, // 85: temporal.server.api.historyservice.v1.RespondWorkflowTaskCompletedResponse.activity_tasks:type_name -> temporal.api.workflowservice.v1.PollActivityTaskQueueResponse + 178, // 86: temporal.server.api.historyservice.v1.RespondWorkflowTaskCompletedResponse.new_workflow_task:type_name -> temporal.api.workflowservice.v1.PollWorkflowTaskQueueResponse + 202, // 87: temporal.server.api.historyservice.v1.RespondWorkflowTaskFailedRequest.failed_request:type_name -> temporal.api.workflowservice.v1.RespondWorkflowTaskFailedRequest + 181, // 88: temporal.server.api.historyservice.v1.IsWorkflowTaskValidRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution + 177, // 89: temporal.server.api.historyservice.v1.IsWorkflowTaskValidRequest.clock:type_name -> temporal.server.api.clock.v1.VectorClock + 203, // 90: temporal.server.api.historyservice.v1.RecordActivityTaskHeartbeatRequest.heartbeat_request:type_name -> temporal.api.workflowservice.v1.RecordActivityTaskHeartbeatRequest + 204, // 91: temporal.server.api.historyservice.v1.RespondActivityTaskCompletedRequest.complete_request:type_name -> temporal.api.workflowservice.v1.RespondActivityTaskCompletedRequest + 205, // 92: temporal.server.api.historyservice.v1.RespondActivityTaskFailedRequest.failed_request:type_name -> temporal.api.workflowservice.v1.RespondActivityTaskFailedRequest + 206, // 93: temporal.server.api.historyservice.v1.RespondActivityTaskCanceledRequest.cancel_request:type_name -> temporal.api.workflowservice.v1.RespondActivityTaskCanceledRequest + 181, // 94: temporal.server.api.historyservice.v1.IsActivityTaskValidRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution + 177, // 95: temporal.server.api.historyservice.v1.IsActivityTaskValidRequest.clock:type_name -> temporal.server.api.clock.v1.VectorClock + 207, // 96: temporal.server.api.historyservice.v1.SignalWorkflowExecutionRequest.signal_request:type_name -> temporal.api.workflowservice.v1.SignalWorkflowExecutionRequest + 181, // 97: temporal.server.api.historyservice.v1.SignalWorkflowExecutionRequest.external_workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution + 208, // 98: temporal.server.api.historyservice.v1.SignalWithStartWorkflowExecutionRequest.signal_with_start_request:type_name -> temporal.api.workflowservice.v1.SignalWithStartWorkflowExecutionRequest + 181, // 99: temporal.server.api.historyservice.v1.RemoveSignalMutableStateRequest.workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution + 209, // 100: temporal.server.api.historyservice.v1.TerminateWorkflowExecutionRequest.terminate_request:type_name -> temporal.api.workflowservice.v1.TerminateWorkflowExecutionRequest + 181, // 101: temporal.server.api.historyservice.v1.TerminateWorkflowExecutionRequest.external_workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution + 181, // 102: temporal.server.api.historyservice.v1.DeleteWorkflowExecutionRequest.workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution + 210, // 103: temporal.server.api.historyservice.v1.ResetWorkflowExecutionRequest.reset_request:type_name -> temporal.api.workflowservice.v1.ResetWorkflowExecutionRequest + 211, // 104: temporal.server.api.historyservice.v1.RequestCancelWorkflowExecutionRequest.cancel_request:type_name -> temporal.api.workflowservice.v1.RequestCancelWorkflowExecutionRequest + 181, // 105: temporal.server.api.historyservice.v1.RequestCancelWorkflowExecutionRequest.external_workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution + 181, // 106: temporal.server.api.historyservice.v1.ScheduleWorkflowTaskRequest.workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution + 177, // 107: temporal.server.api.historyservice.v1.ScheduleWorkflowTaskRequest.child_clock:type_name -> temporal.server.api.clock.v1.VectorClock + 177, // 108: temporal.server.api.historyservice.v1.ScheduleWorkflowTaskRequest.parent_clock:type_name -> temporal.server.api.clock.v1.VectorClock + 181, // 109: temporal.server.api.historyservice.v1.VerifyFirstWorkflowTaskScheduledRequest.workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution + 177, // 110: temporal.server.api.historyservice.v1.VerifyFirstWorkflowTaskScheduledRequest.clock:type_name -> temporal.server.api.clock.v1.VectorClock + 181, // 111: temporal.server.api.historyservice.v1.RecordChildExecutionCompletedRequest.parent_execution:type_name -> temporal.api.common.v1.WorkflowExecution + 181, // 112: temporal.server.api.historyservice.v1.RecordChildExecutionCompletedRequest.child_execution:type_name -> temporal.api.common.v1.WorkflowExecution + 197, // 113: temporal.server.api.historyservice.v1.RecordChildExecutionCompletedRequest.completion_event:type_name -> temporal.api.history.v1.HistoryEvent + 177, // 114: temporal.server.api.historyservice.v1.RecordChildExecutionCompletedRequest.clock:type_name -> temporal.server.api.clock.v1.VectorClock + 181, // 115: temporal.server.api.historyservice.v1.VerifyChildExecutionCompletionRecordedRequest.parent_execution:type_name -> temporal.api.common.v1.WorkflowExecution + 181, // 116: temporal.server.api.historyservice.v1.VerifyChildExecutionCompletionRecordedRequest.child_execution:type_name -> temporal.api.common.v1.WorkflowExecution + 177, // 117: temporal.server.api.historyservice.v1.VerifyChildExecutionCompletionRecordedRequest.clock:type_name -> temporal.server.api.clock.v1.VectorClock + 212, // 118: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionRequest.request:type_name -> temporal.api.workflowservice.v1.DescribeWorkflowExecutionRequest + 213, // 119: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.execution_config:type_name -> temporal.api.workflow.v1.WorkflowExecutionConfig + 214, // 120: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.workflow_execution_info:type_name -> temporal.api.workflow.v1.WorkflowExecutionInfo + 215, // 121: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.pending_activities:type_name -> temporal.api.workflow.v1.PendingActivityInfo + 216, // 122: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.pending_children:type_name -> temporal.api.workflow.v1.PendingChildExecutionInfo + 217, // 123: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.pending_workflow_task:type_name -> temporal.api.workflow.v1.PendingWorkflowTaskInfo + 218, // 124: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.callbacks:type_name -> temporal.api.workflow.v1.CallbackInfo + 219, // 125: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.pending_nexus_operations:type_name -> temporal.api.workflow.v1.PendingNexusOperationInfo + 220, // 126: temporal.server.api.historyservice.v1.DescribeWorkflowExecutionResponse.workflow_extended_info:type_name -> temporal.api.workflow.v1.WorkflowExecutionExtendedInfo + 181, // 127: temporal.server.api.historyservice.v1.ReplicateEventsV2Request.workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution + 182, // 128: temporal.server.api.historyservice.v1.ReplicateEventsV2Request.version_history_items:type_name -> temporal.server.api.history.v1.VersionHistoryItem + 221, // 129: temporal.server.api.historyservice.v1.ReplicateEventsV2Request.events:type_name -> temporal.api.common.v1.DataBlob + 221, // 130: temporal.server.api.historyservice.v1.ReplicateEventsV2Request.new_run_events:type_name -> temporal.api.common.v1.DataBlob + 222, // 131: temporal.server.api.historyservice.v1.ReplicateEventsV2Request.base_execution_info:type_name -> temporal.server.api.workflow.v1.BaseExecutionInfo + 223, // 132: temporal.server.api.historyservice.v1.ReplicateWorkflowStateRequest.workflow_state:type_name -> temporal.server.api.persistence.v1.WorkflowMutableState + 167, // 133: temporal.server.api.historyservice.v1.SyncShardStatusRequest.status_time:type_name -> google.protobuf.Timestamp + 167, // 134: temporal.server.api.historyservice.v1.SyncActivityRequest.scheduled_time:type_name -> google.protobuf.Timestamp + 167, // 135: temporal.server.api.historyservice.v1.SyncActivityRequest.started_time:type_name -> google.protobuf.Timestamp + 167, // 136: temporal.server.api.historyservice.v1.SyncActivityRequest.last_heartbeat_time:type_name -> google.protobuf.Timestamp + 170, // 137: temporal.server.api.historyservice.v1.SyncActivityRequest.details:type_name -> temporal.api.common.v1.Payloads + 169, // 138: temporal.server.api.historyservice.v1.SyncActivityRequest.last_failure:type_name -> temporal.api.failure.v1.Failure + 224, // 139: temporal.server.api.historyservice.v1.SyncActivityRequest.version_history:type_name -> temporal.server.api.history.v1.VersionHistory + 222, // 140: temporal.server.api.historyservice.v1.SyncActivityRequest.base_execution_info:type_name -> temporal.server.api.workflow.v1.BaseExecutionInfo + 167, // 141: temporal.server.api.historyservice.v1.SyncActivityRequest.first_scheduled_time:type_name -> google.protobuf.Timestamp + 167, // 142: temporal.server.api.historyservice.v1.SyncActivityRequest.last_attempt_complete_time:type_name -> google.protobuf.Timestamp + 171, // 143: temporal.server.api.historyservice.v1.SyncActivityRequest.retry_initial_interval:type_name -> google.protobuf.Duration + 171, // 144: temporal.server.api.historyservice.v1.SyncActivityRequest.retry_maximum_interval:type_name -> google.protobuf.Duration + 64, // 145: temporal.server.api.historyservice.v1.SyncActivitiesRequest.activities_info:type_name -> temporal.server.api.historyservice.v1.ActivitySyncInfo + 167, // 146: temporal.server.api.historyservice.v1.ActivitySyncInfo.scheduled_time:type_name -> google.protobuf.Timestamp + 167, // 147: temporal.server.api.historyservice.v1.ActivitySyncInfo.started_time:type_name -> google.protobuf.Timestamp + 167, // 148: temporal.server.api.historyservice.v1.ActivitySyncInfo.last_heartbeat_time:type_name -> google.protobuf.Timestamp + 170, // 149: temporal.server.api.historyservice.v1.ActivitySyncInfo.details:type_name -> temporal.api.common.v1.Payloads + 169, // 150: temporal.server.api.historyservice.v1.ActivitySyncInfo.last_failure:type_name -> temporal.api.failure.v1.Failure + 224, // 151: temporal.server.api.historyservice.v1.ActivitySyncInfo.version_history:type_name -> temporal.server.api.history.v1.VersionHistory + 167, // 152: temporal.server.api.historyservice.v1.ActivitySyncInfo.first_scheduled_time:type_name -> google.protobuf.Timestamp + 167, // 153: temporal.server.api.historyservice.v1.ActivitySyncInfo.last_attempt_complete_time:type_name -> google.protobuf.Timestamp + 171, // 154: temporal.server.api.historyservice.v1.ActivitySyncInfo.retry_initial_interval:type_name -> google.protobuf.Duration + 171, // 155: temporal.server.api.historyservice.v1.ActivitySyncInfo.retry_maximum_interval:type_name -> google.protobuf.Duration + 181, // 156: temporal.server.api.historyservice.v1.DescribeMutableStateRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution + 223, // 157: temporal.server.api.historyservice.v1.DescribeMutableStateResponse.cache_mutable_state:type_name -> temporal.server.api.persistence.v1.WorkflowMutableState + 223, // 158: temporal.server.api.historyservice.v1.DescribeMutableStateResponse.database_mutable_state:type_name -> temporal.server.api.persistence.v1.WorkflowMutableState + 181, // 159: temporal.server.api.historyservice.v1.DescribeHistoryHostRequest.workflow_execution:type_name -> temporal.api.common.v1.WorkflowExecution + 225, // 160: temporal.server.api.historyservice.v1.DescribeHistoryHostResponse.namespace_cache:type_name -> temporal.server.api.namespace.v1.NamespaceCacheInfo + 226, // 161: temporal.server.api.historyservice.v1.GetShardResponse.shard_info:type_name -> temporal.server.api.persistence.v1.ShardInfo + 167, // 162: temporal.server.api.historyservice.v1.RemoveTaskRequest.visibility_time:type_name -> google.protobuf.Timestamp + 227, // 163: temporal.server.api.historyservice.v1.GetReplicationMessagesRequest.tokens:type_name -> temporal.server.api.replication.v1.ReplicationToken + 160, // 164: temporal.server.api.historyservice.v1.GetReplicationMessagesResponse.shard_messages:type_name -> temporal.server.api.historyservice.v1.GetReplicationMessagesResponse.ShardMessagesEntry + 228, // 165: temporal.server.api.historyservice.v1.GetDLQReplicationMessagesRequest.task_infos:type_name -> temporal.server.api.replication.v1.ReplicationTaskInfo + 229, // 166: temporal.server.api.historyservice.v1.GetDLQReplicationMessagesResponse.replication_tasks:type_name -> temporal.server.api.replication.v1.ReplicationTask + 230, // 167: temporal.server.api.historyservice.v1.QueryWorkflowRequest.request:type_name -> temporal.api.workflowservice.v1.QueryWorkflowRequest + 231, // 168: temporal.server.api.historyservice.v1.QueryWorkflowResponse.response:type_name -> temporal.api.workflowservice.v1.QueryWorkflowResponse + 232, // 169: temporal.server.api.historyservice.v1.ReapplyEventsRequest.request:type_name -> temporal.server.api.adminservice.v1.ReapplyEventsRequest + 233, // 170: temporal.server.api.historyservice.v1.GetDLQMessagesRequest.type:type_name -> temporal.server.api.enums.v1.DeadLetterQueueType + 233, // 171: temporal.server.api.historyservice.v1.GetDLQMessagesResponse.type:type_name -> temporal.server.api.enums.v1.DeadLetterQueueType + 229, // 172: temporal.server.api.historyservice.v1.GetDLQMessagesResponse.replication_tasks:type_name -> temporal.server.api.replication.v1.ReplicationTask + 228, // 173: temporal.server.api.historyservice.v1.GetDLQMessagesResponse.replication_tasks_info:type_name -> temporal.server.api.replication.v1.ReplicationTaskInfo + 233, // 174: temporal.server.api.historyservice.v1.PurgeDLQMessagesRequest.type:type_name -> temporal.server.api.enums.v1.DeadLetterQueueType + 233, // 175: temporal.server.api.historyservice.v1.MergeDLQMessagesRequest.type:type_name -> temporal.server.api.enums.v1.DeadLetterQueueType + 234, // 176: temporal.server.api.historyservice.v1.RefreshWorkflowTasksRequest.request:type_name -> temporal.server.api.adminservice.v1.RefreshWorkflowTasksRequest + 181, // 177: temporal.server.api.historyservice.v1.GenerateLastHistoryReplicationTasksRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution + 96, // 178: temporal.server.api.historyservice.v1.GetReplicationStatusResponse.shards:type_name -> temporal.server.api.historyservice.v1.ShardReplicationStatus + 167, // 179: temporal.server.api.historyservice.v1.ShardReplicationStatus.shard_local_time:type_name -> google.protobuf.Timestamp + 161, // 180: temporal.server.api.historyservice.v1.ShardReplicationStatus.remote_clusters:type_name -> temporal.server.api.historyservice.v1.ShardReplicationStatus.RemoteClustersEntry + 162, // 181: temporal.server.api.historyservice.v1.ShardReplicationStatus.handover_namespaces:type_name -> temporal.server.api.historyservice.v1.ShardReplicationStatus.HandoverNamespacesEntry + 167, // 182: temporal.server.api.historyservice.v1.ShardReplicationStatus.max_replication_task_visibility_time:type_name -> google.protobuf.Timestamp + 167, // 183: temporal.server.api.historyservice.v1.ShardReplicationStatusPerCluster.acked_task_visibility_time:type_name -> google.protobuf.Timestamp + 181, // 184: temporal.server.api.historyservice.v1.RebuildMutableStateRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution + 181, // 185: temporal.server.api.historyservice.v1.ImportWorkflowExecutionRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution + 221, // 186: temporal.server.api.historyservice.v1.ImportWorkflowExecutionRequest.history_batches:type_name -> temporal.api.common.v1.DataBlob + 224, // 187: temporal.server.api.historyservice.v1.ImportWorkflowExecutionRequest.version_history:type_name -> temporal.server.api.history.v1.VersionHistory + 181, // 188: temporal.server.api.historyservice.v1.DeleteWorkflowVisibilityRecordRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution + 167, // 189: temporal.server.api.historyservice.v1.DeleteWorkflowVisibilityRecordRequest.workflow_start_time:type_name -> google.protobuf.Timestamp + 167, // 190: temporal.server.api.historyservice.v1.DeleteWorkflowVisibilityRecordRequest.workflow_close_time:type_name -> google.protobuf.Timestamp + 235, // 191: temporal.server.api.historyservice.v1.UpdateWorkflowExecutionRequest.request:type_name -> temporal.api.workflowservice.v1.UpdateWorkflowExecutionRequest + 236, // 192: temporal.server.api.historyservice.v1.UpdateWorkflowExecutionResponse.response:type_name -> temporal.api.workflowservice.v1.UpdateWorkflowExecutionResponse + 237, // 193: temporal.server.api.historyservice.v1.StreamWorkflowReplicationMessagesRequest.sync_replication_state:type_name -> temporal.server.api.replication.v1.SyncReplicationState + 238, // 194: temporal.server.api.historyservice.v1.StreamWorkflowReplicationMessagesResponse.messages:type_name -> temporal.server.api.replication.v1.WorkflowReplicationMessages + 239, // 195: temporal.server.api.historyservice.v1.PollWorkflowExecutionUpdateRequest.request:type_name -> temporal.api.workflowservice.v1.PollWorkflowExecutionUpdateRequest + 240, // 196: temporal.server.api.historyservice.v1.PollWorkflowExecutionUpdateResponse.response:type_name -> temporal.api.workflowservice.v1.PollWorkflowExecutionUpdateResponse + 241, // 197: temporal.server.api.historyservice.v1.GetWorkflowExecutionHistoryRequest.request:type_name -> temporal.api.workflowservice.v1.GetWorkflowExecutionHistoryRequest + 242, // 198: temporal.server.api.historyservice.v1.GetWorkflowExecutionHistoryResponse.response:type_name -> temporal.api.workflowservice.v1.GetWorkflowExecutionHistoryResponse + 195, // 199: temporal.server.api.historyservice.v1.GetWorkflowExecutionHistoryResponse.history:type_name -> temporal.api.history.v1.History + 242, // 200: temporal.server.api.historyservice.v1.GetWorkflowExecutionHistoryResponseWithRaw.response:type_name -> temporal.api.workflowservice.v1.GetWorkflowExecutionHistoryResponse + 243, // 201: temporal.server.api.historyservice.v1.GetWorkflowExecutionHistoryReverseRequest.request:type_name -> temporal.api.workflowservice.v1.GetWorkflowExecutionHistoryReverseRequest + 244, // 202: temporal.server.api.historyservice.v1.GetWorkflowExecutionHistoryReverseResponse.response:type_name -> temporal.api.workflowservice.v1.GetWorkflowExecutionHistoryReverseResponse + 245, // 203: temporal.server.api.historyservice.v1.GetWorkflowExecutionRawHistoryV2Request.request:type_name -> temporal.server.api.adminservice.v1.GetWorkflowExecutionRawHistoryV2Request + 246, // 204: temporal.server.api.historyservice.v1.GetWorkflowExecutionRawHistoryV2Response.response:type_name -> temporal.server.api.adminservice.v1.GetWorkflowExecutionRawHistoryV2Response + 247, // 205: temporal.server.api.historyservice.v1.GetWorkflowExecutionRawHistoryRequest.request:type_name -> temporal.server.api.adminservice.v1.GetWorkflowExecutionRawHistoryRequest + 248, // 206: temporal.server.api.historyservice.v1.GetWorkflowExecutionRawHistoryResponse.response:type_name -> temporal.server.api.adminservice.v1.GetWorkflowExecutionRawHistoryResponse + 249, // 207: temporal.server.api.historyservice.v1.ForceDeleteWorkflowExecutionRequest.request:type_name -> temporal.server.api.adminservice.v1.DeleteWorkflowExecutionRequest + 250, // 208: temporal.server.api.historyservice.v1.ForceDeleteWorkflowExecutionResponse.response:type_name -> temporal.server.api.adminservice.v1.DeleteWorkflowExecutionResponse + 251, // 209: temporal.server.api.historyservice.v1.GetDLQTasksRequest.dlq_key:type_name -> temporal.server.api.common.v1.HistoryDLQKey + 252, // 210: temporal.server.api.historyservice.v1.GetDLQTasksResponse.dlq_tasks:type_name -> temporal.server.api.common.v1.HistoryDLQTask + 251, // 211: temporal.server.api.historyservice.v1.DeleteDLQTasksRequest.dlq_key:type_name -> temporal.server.api.common.v1.HistoryDLQKey + 253, // 212: temporal.server.api.historyservice.v1.DeleteDLQTasksRequest.inclusive_max_task_metadata:type_name -> temporal.server.api.common.v1.HistoryDLQTaskMetadata + 163, // 213: temporal.server.api.historyservice.v1.ListQueuesResponse.queues:type_name -> temporal.server.api.historyservice.v1.ListQueuesResponse.QueueInfo + 164, // 214: temporal.server.api.historyservice.v1.AddTasksRequest.tasks:type_name -> temporal.server.api.historyservice.v1.AddTasksRequest.Task + 254, // 215: temporal.server.api.historyservice.v1.ListTasksRequest.request:type_name -> temporal.server.api.adminservice.v1.ListHistoryTasksRequest + 255, // 216: temporal.server.api.historyservice.v1.ListTasksResponse.response:type_name -> temporal.server.api.adminservice.v1.ListHistoryTasksResponse + 256, // 217: temporal.server.api.historyservice.v1.CompleteNexusOperationChasmRequest.completion:type_name -> temporal.server.api.token.v1.NexusOperationCompletion + 257, // 218: temporal.server.api.historyservice.v1.CompleteNexusOperationChasmRequest.success:type_name -> temporal.api.common.v1.Payload + 169, // 219: temporal.server.api.historyservice.v1.CompleteNexusOperationChasmRequest.failure:type_name -> temporal.api.failure.v1.Failure + 167, // 220: temporal.server.api.historyservice.v1.CompleteNexusOperationChasmRequest.close_time:type_name -> google.protobuf.Timestamp + 256, // 221: temporal.server.api.historyservice.v1.CompleteNexusOperationRequest.completion:type_name -> temporal.server.api.token.v1.NexusOperationCompletion + 257, // 222: temporal.server.api.historyservice.v1.CompleteNexusOperationRequest.success:type_name -> temporal.api.common.v1.Payload + 258, // 223: temporal.server.api.historyservice.v1.CompleteNexusOperationRequest.failure:type_name -> temporal.api.nexus.v1.Failure + 167, // 224: temporal.server.api.historyservice.v1.CompleteNexusOperationRequest.start_time:type_name -> google.protobuf.Timestamp + 180, // 225: temporal.server.api.historyservice.v1.CompleteNexusOperationRequest.links:type_name -> temporal.api.common.v1.Link + 259, // 226: temporal.server.api.historyservice.v1.InvokeStateMachineMethodRequest.ref:type_name -> temporal.server.api.persistence.v1.StateMachineRef + 260, // 227: temporal.server.api.historyservice.v1.DeepHealthCheckResponse.state:type_name -> temporal.server.api.enums.v1.HealthState + 181, // 228: temporal.server.api.historyservice.v1.SyncWorkflowStateRequest.execution:type_name -> temporal.api.common.v1.WorkflowExecution + 183, // 229: temporal.server.api.historyservice.v1.SyncWorkflowStateRequest.versioned_transition:type_name -> temporal.server.api.persistence.v1.VersionedTransition + 187, // 230: temporal.server.api.historyservice.v1.SyncWorkflowStateRequest.version_histories:type_name -> temporal.server.api.history.v1.VersionHistories + 261, // 231: temporal.server.api.historyservice.v1.SyncWorkflowStateResponse.versioned_transition_artifact:type_name -> temporal.server.api.replication.v1.VersionedTransitionArtifact + 262, // 232: temporal.server.api.historyservice.v1.UpdateActivityOptionsRequest.update_request:type_name -> temporal.api.workflowservice.v1.UpdateActivityOptionsRequest + 263, // 233: temporal.server.api.historyservice.v1.UpdateActivityOptionsResponse.activity_options:type_name -> temporal.api.activity.v1.ActivityOptions + 264, // 234: temporal.server.api.historyservice.v1.PauseActivityRequest.frontend_request:type_name -> temporal.api.workflowservice.v1.PauseActivityRequest + 265, // 235: temporal.server.api.historyservice.v1.UnpauseActivityRequest.frontend_request:type_name -> temporal.api.workflowservice.v1.UnpauseActivityRequest + 266, // 236: temporal.server.api.historyservice.v1.ResetActivityRequest.frontend_request:type_name -> temporal.api.workflowservice.v1.ResetActivityRequest + 267, // 237: temporal.server.api.historyservice.v1.UpdateWorkflowExecutionOptionsRequest.update_request:type_name -> temporal.api.workflowservice.v1.UpdateWorkflowExecutionOptionsRequest + 268, // 238: temporal.server.api.historyservice.v1.UpdateWorkflowExecutionOptionsResponse.workflow_execution_options:type_name -> temporal.api.workflow.v1.WorkflowExecutionOptions + 269, // 239: temporal.server.api.historyservice.v1.PauseWorkflowExecutionRequest.pause_request:type_name -> temporal.api.workflowservice.v1.PauseWorkflowExecutionRequest + 270, // 240: temporal.server.api.historyservice.v1.UnpauseWorkflowExecutionRequest.unpause_request:type_name -> temporal.api.workflowservice.v1.UnpauseWorkflowExecutionRequest + 1, // 241: temporal.server.api.historyservice.v1.ExecuteMultiOperationRequest.Operation.start_workflow:type_name -> temporal.server.api.historyservice.v1.StartWorkflowExecutionRequest + 105, // 242: temporal.server.api.historyservice.v1.ExecuteMultiOperationRequest.Operation.update_workflow:type_name -> temporal.server.api.historyservice.v1.UpdateWorkflowExecutionRequest + 2, // 243: temporal.server.api.historyservice.v1.ExecuteMultiOperationResponse.Response.start_workflow:type_name -> temporal.server.api.historyservice.v1.StartWorkflowExecutionResponse + 106, // 244: temporal.server.api.historyservice.v1.ExecuteMultiOperationResponse.Response.update_workflow:type_name -> temporal.server.api.historyservice.v1.UpdateWorkflowExecutionResponse + 271, // 245: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponse.QueriesEntry.value:type_name -> temporal.api.query.v1.WorkflowQuery + 271, // 246: temporal.server.api.historyservice.v1.RecordWorkflowTaskStartedResponseWithRawHistory.QueriesEntry.value:type_name -> temporal.api.query.v1.WorkflowQuery + 272, // 247: temporal.server.api.historyservice.v1.GetReplicationMessagesResponse.ShardMessagesEntry.value:type_name -> temporal.server.api.replication.v1.ReplicationMessages + 98, // 248: temporal.server.api.historyservice.v1.ShardReplicationStatus.RemoteClustersEntry.value:type_name -> temporal.server.api.historyservice.v1.ShardReplicationStatusPerCluster + 97, // 249: temporal.server.api.historyservice.v1.ShardReplicationStatus.HandoverNamespacesEntry.value:type_name -> temporal.server.api.historyservice.v1.HandoverNamespaceInfo + 221, // 250: temporal.server.api.historyservice.v1.AddTasksRequest.Task.blob:type_name -> temporal.api.common.v1.DataBlob + 273, // 251: temporal.server.api.historyservice.v1.routing:extendee -> google.protobuf.MessageOptions + 0, // 252: temporal.server.api.historyservice.v1.routing:type_name -> temporal.server.api.historyservice.v1.RoutingOptions + 253, // [253:253] is the sub-list for method output_type + 253, // [253:253] is the sub-list for method input_type + 252, // [252:253] is the sub-list for extension type_name + 251, // [251:252] is the sub-list for extension extendee + 0, // [0:251] is the sub-list for field type_name } func init() { file_temporal_server_api_historyservice_v1_request_response_proto_init() } diff --git a/common/worker_versioning/worker_versioning.go b/common/worker_versioning/worker_versioning.go index 6c223c8387..b53647c3d2 100644 --- a/common/worker_versioning/worker_versioning.go +++ b/common/worker_versioning/worker_versioning.go @@ -669,13 +669,13 @@ func calcRampThreshold(id string) float64 { //revive:disable-next-line:cognitive-complexity,confusing-results,function-result-limit,cyclomatic func CalculateTaskQueueVersioningInfo(deployments *persistencespb.DeploymentData) ( *deploymentspb.WorkerDeploymentVersion, // current version - int64, // current revision number - time.Time, // current update time + int64, // current revision number + time.Time, // current update time *deploymentspb.WorkerDeploymentVersion, // ramping version - bool, // is ramping (ramping_since_time != nil) - float32, // ramp percentage - int64, // ramping revision number - time.Time, // ramping update time + bool, // is ramping (ramping_since_time != nil) + float32, // ramp percentage + int64, // ramping revision number + time.Time, // ramping update time ) { if deployments == nil { return nil, 0, time.Time{}, nil, false, 0, 0, time.Time{} diff --git a/proto/internal/temporal/server/api/historyservice/v1/request_response.proto b/proto/internal/temporal/server/api/historyservice/v1/request_response.proto index af2d853b4b..189a73a643 100644 --- a/proto/internal/temporal/server/api/historyservice/v1/request_response.proto +++ b/proto/internal/temporal/server/api/historyservice/v1/request_response.proto @@ -247,6 +247,9 @@ message RecordWorkflowTaskStartedRequest { // Revision number that was sent by matching when the task was dispatched. Used to resolve eventual consistency issues // that may arise due to stale routing configs in task queue partitions. int64 task_dispatch_revision_number = 12; + // Target worker deployment version according to matching when starting the task. + // Computed after matching with a poller, right before calling RecordWorkflowTaskStarted. + temporal.api.deployment.v1.WorkerDeploymentVersion target_deployment_version = 13; } message RecordWorkflowTaskStartedResponse { diff --git a/service/history/api/create_workflow_util.go b/service/history/api/create_workflow_util.go index 800b96b16c..fd61e3f6a8 100644 --- a/service/history/api/create_workflow_util.go +++ b/service/history/api/create_workflow_util.go @@ -114,6 +114,7 @@ func NewWorkflowWithSignal( nil, nil, false, + nil, ) if err != nil { // Unable to add WorkflowTaskStarted event to history diff --git a/service/history/api/recordworkflowtaskstarted/api.go b/service/history/api/recordworkflowtaskstarted/api.go index 3cbf2194ec..bd2a09b2a5 100644 --- a/service/history/api/recordworkflowtaskstarted/api.go +++ b/service/history/api/recordworkflowtaskstarted/api.go @@ -169,6 +169,7 @@ func Invoke( req.GetBuildIdRedirectInfo(), workflowLease.GetContext().UpdateRegistry(ctx), false, + req.TargetDeploymentVersion, ) if err != nil { // Unable to add WorkflowTaskStarted event to history diff --git a/service/history/api/respondworkflowtaskcompleted/api.go b/service/history/api/respondworkflowtaskcompleted/api.go index 3750cfbc90..03872fe606 100644 --- a/service/history/api/respondworkflowtaskcompleted/api.go +++ b/service/history/api/respondworkflowtaskcompleted/api.go @@ -577,6 +577,7 @@ func (handler *WorkflowTaskCompletedHandler) Invoke( nil, workflowLease.GetContext().UpdateRegistry(ctx), false, + nil, ) if err != nil { return nil, err @@ -701,6 +702,7 @@ func (handler *WorkflowTaskCompletedHandler) Invoke( nil, workflowLease.GetContext().UpdateRegistry(ctx), false, + nil, ) if err != nil { return nil, err diff --git a/service/history/api/respondworkflowtaskcompleted/api_test.go b/service/history/api/respondworkflowtaskcompleted/api_test.go index 6f58764a21..bc34df7008 100644 --- a/service/history/api/respondworkflowtaskcompleted/api_test.go +++ b/service/history/api/respondworkflowtaskcompleted/api_test.go @@ -576,6 +576,7 @@ func (s *WorkflowTaskCompletedHandlerSuite) createSentUpdate(tv *testvars.TestVa nil, nil, false, + nil, ) taskToken := &tokenspb.Task{ Attempt: 1, diff --git a/service/history/history_engine_test.go b/service/history/history_engine_test.go index 612bc9643b..7a83ac3256 100644 --- a/service/history/history_engine_test.go +++ b/service/history/history_engine_test.go @@ -6475,6 +6475,7 @@ func addWorkflowTaskStartedEventWithRequestID(ms historyi.MutableState, schedule nil, nil, false, + nil, ) return event diff --git a/service/history/interfaces/mutable_state.go b/service/history/interfaces/mutable_state.go index 89d496d930..ff29f9b52d 100644 --- a/service/history/interfaces/mutable_state.go +++ b/service/history/interfaces/mutable_state.go @@ -73,7 +73,7 @@ type ( AddFirstWorkflowTaskScheduled(parentClock *clockspb.VectorClock, event *historypb.HistoryEvent, bypassTaskGeneration bool) (int64, error) AddWorkflowTaskScheduledEvent(bypassTaskGeneration bool, workflowTaskType enumsspb.WorkflowTaskType) (*WorkflowTaskInfo, error) AddWorkflowTaskScheduledEventAsHeartbeat(bypassTaskGeneration bool, originalScheduledTimestamp *timestamppb.Timestamp, workflowTaskType enumsspb.WorkflowTaskType) (*WorkflowTaskInfo, error) - AddWorkflowTaskStartedEvent(int64, string, *taskqueuepb.TaskQueue, string, *commonpb.WorkerVersionStamp, *taskqueuespb.BuildIdRedirectInfo, update.Registry, bool) (*historypb.HistoryEvent, *WorkflowTaskInfo, error) + AddWorkflowTaskStartedEvent(int64, string, *taskqueuepb.TaskQueue, string, *commonpb.WorkerVersionStamp, *taskqueuespb.BuildIdRedirectInfo, update.Registry, bool, *deploymentpb.WorkerDeploymentVersion) (*historypb.HistoryEvent, *WorkflowTaskInfo, error) AddWorkflowTaskTimedOutEvent(workflowTask *WorkflowTaskInfo) (*historypb.HistoryEvent, error) AddExternalWorkflowExecutionCancelRequested(int64, namespace.Name, namespace.ID, string, string) (*historypb.HistoryEvent, error) AddExternalWorkflowExecutionSignaled(int64, namespace.Name, namespace.ID, string, string, string) (*historypb.HistoryEvent, error) diff --git a/service/history/ndc/workflow_resetter.go b/service/history/ndc/workflow_resetter.go index 4abf39ab31..b9023b7808 100644 --- a/service/history/ndc/workflow_resetter.go +++ b/service/history/ndc/workflow_resetter.go @@ -527,6 +527,7 @@ func (r *workflowResetterImpl) failWorkflowTask( nil, // skipping versioning checks because this task is not actually dispatched but will fail immediately. true, + nil, ) if err != nil { return err diff --git a/service/history/workflow/mutable_state_impl.go b/service/history/workflow/mutable_state_impl.go index a70f8df852..62b830368b 100644 --- a/service/history/workflow/mutable_state_impl.go +++ b/service/history/workflow/mutable_state_impl.go @@ -3192,12 +3192,13 @@ func (ms *MutableStateImpl) AddWorkflowTaskStartedEvent( redirectInfo *taskqueuespb.BuildIdRedirectInfo, updateReg update.Registry, skipVersioningCheck bool, + targetDeploymentVersion *deploymentpb.WorkerDeploymentVersion, ) (*historypb.HistoryEvent, *historyi.WorkflowTaskInfo, error) { opTag := tag.WorkflowActionWorkflowTaskStarted if err := ms.checkMutability(opTag); err != nil { return nil, nil, err } - return ms.workflowTaskManager.AddWorkflowTaskStartedEvent(scheduledEventID, requestID, taskQueue, identity, versioningStamp, redirectInfo, skipVersioningCheck, updateReg) + return ms.workflowTaskManager.AddWorkflowTaskStartedEvent(scheduledEventID, requestID, taskQueue, identity, versioningStamp, redirectInfo, skipVersioningCheck, updateReg, targetDeploymentVersion) } func (ms *MutableStateImpl) ApplyWorkflowTaskStartedEvent( diff --git a/service/history/workflow/workflow_task_state_machine.go b/service/history/workflow/workflow_task_state_machine.go index bbe602f70e..7a79018be5 100644 --- a/service/history/workflow/workflow_task_state_machine.go +++ b/service/history/workflow/workflow_task_state_machine.go @@ -8,6 +8,7 @@ import ( "time" commonpb "go.temporal.io/api/common/v1" + deploymentpb "go.temporal.io/api/deployment/v1" enumspb "go.temporal.io/api/enums/v1" failurepb "go.temporal.io/api/failure/v1" historypb "go.temporal.io/api/history/v1" @@ -451,6 +452,7 @@ func (m *workflowTaskStateMachine) AddWorkflowTaskStartedEvent( redirectInfo *taskqueuespb.BuildIdRedirectInfo, skipVersioningCheck bool, updateReg update.Registry, + targetDeploymentVersion *deploymentpb.WorkerDeploymentVersion, ) (*historypb.HistoryEvent, *historyi.WorkflowTaskInfo, error) { opTag := tag.WorkflowActionWorkflowTaskStarted workflowTask := m.GetWorkflowTaskByID(scheduledEventID) @@ -477,6 +479,13 @@ func (m *workflowTaskStateMachine) AddWorkflowTaskStartedEvent( suggestContinueAsNew = cmp.Or(suggestContinueAsNew, updateReg.SuggestContinueAsNew()) } + if m.ms.GetEffectiveVersioningBehavior() == enumspb.VERSIONING_BEHAVIOR_PINNED_UNTIL_CONTINUE_AS_NEW && targetDeploymentVersion != nil { + if currentDeploymentVersion := m.ms.GetEffectiveDeployment(); currentDeploymentVersion.BuildId != targetDeploymentVersion.BuildId || + currentDeploymentVersion.SeriesName != targetDeploymentVersion.DeploymentName { + suggestContinueAsNew = cmp.Or(suggestContinueAsNew, true) + } + } + workflowTask, scheduledEventCreatedForRedirect, redirectCounter, err := m.processBuildIdRedirectInfo(versioningStamp, workflowTask, taskQueue, redirectInfo, skipVersioningCheck) if err != nil { return nil, nil, err diff --git a/service/matching/matching_engine.go b/service/matching/matching_engine.go index 333da88f62..acb86b6bd4 100644 --- a/service/matching/matching_engine.go +++ b/service/matching/matching_engine.go @@ -2996,6 +2996,7 @@ func (e *matchingEngineImpl) recordWorkflowTaskStarted( VersionDirective: task.event.Data.VersionDirective, TaskDispatchRevisionNumber: task.taskDispatchRevisionNumber, Stamp: task.event.Data.GetStamp(), + TargetDeploymentVersion: worker_versioning.ExternalWorkerDeploymentVersionFromVersion(task.targetWorkerDeploymentVersion), } resp, err := e.historyClient.RecordWorkflowTaskStarted(ctx, recordStartedRequest) @@ -3055,6 +3056,7 @@ func (e *matchingEngineImpl) recordActivityTaskStarted( ScheduledDeployment: worker_versioning.DirectiveDeployment(task.event.Data.VersionDirective), VersionDirective: task.event.Data.VersionDirective, TaskDispatchRevisionNumber: task.taskDispatchRevisionNumber, + // TODO(carlydf): Do I send target version here? probably not but just double check } return e.historyClient.RecordActivityTaskStarted(ctx, recordStartedRequest) diff --git a/service/matching/task.go b/service/matching/task.go index 2b989a0481..376f3b978e 100644 --- a/service/matching/task.go +++ b/service/matching/task.go @@ -7,6 +7,7 @@ import ( commonpb "go.temporal.io/api/common/v1" taskqueuepb "go.temporal.io/api/taskqueue/v1" + deploymentspb "go.temporal.io/server/api/deployment/v1" enumsspb "go.temporal.io/server/api/enums/v1" "go.temporal.io/server/api/matchingservice/v1" persistencespb "go.temporal.io/server/api/persistence/v1" @@ -66,6 +67,8 @@ type ( recycleToken func(*internalTask) removeFromMatcher atomic.Pointer[func()] + targetWorkerDeploymentVersion *deploymentspb.WorkerDeploymentVersion + // These fields are for use by matcherData: waitableMatchResult forwardCtx context.Context // non-nil for sync match task only @@ -107,6 +110,7 @@ func newInternalTaskForSyncMatch( info *persistencespb.TaskInfo, forwardInfo *taskqueuespb.TaskForwardInfo, taskDispatchRevisionNumber int64, + targetVersion *deploymentspb.WorkerDeploymentVersion, ) *internalTask { var redirectInfo *taskqueuespb.BuildIdRedirectInfo // if this task is not forwarded, source can only be history @@ -124,11 +128,12 @@ func newInternalTaskForSyncMatch( TaskId: syncMatchTaskId, }, }, - forwardInfo: forwardInfo, - source: source, - redirectInfo: redirectInfo, - responseC: make(chan taskResponse, 1), - effectivePriority: priorityKey(info.GetPriority().GetPriorityKey()), + forwardInfo: forwardInfo, + source: source, + redirectInfo: redirectInfo, + responseC: make(chan taskResponse, 1), + effectivePriority: priorityKey(info.GetPriority().GetPriorityKey()), + targetWorkerDeploymentVersion: targetVersion, } } diff --git a/service/matching/task_queue_partition_manager.go b/service/matching/task_queue_partition_manager.go index 222e375b70..d16687e086 100644 --- a/service/matching/task_queue_partition_manager.go +++ b/service/matching/task_queue_partition_manager.go @@ -12,6 +12,7 @@ import ( "go.temporal.io/api/serviceerror" taskqueuepb "go.temporal.io/api/taskqueue/v1" "go.temporal.io/api/workflowservice/v1" + deploymentspb "go.temporal.io/server/api/deployment/v1" "go.temporal.io/server/api/matchingservice/v1" persistencespb "go.temporal.io/server/api/persistence/v1" taskqueuespb "go.temporal.io/server/api/taskqueue/v1" @@ -199,12 +200,12 @@ func (pm *taskQueuePartitionManagerImpl) AddTask( directive := params.taskInfo.GetVersionDirective() // spoolQueue will be nil iff task is forwarded. reredirectTask: - spoolQueue, syncMatchQueue, _, taskDispatchRevisionNumber, err := pm.getPhysicalQueuesForAdd(ctx, directive, params.forwardInfo, params.taskInfo.GetRunId(), params.taskInfo.GetWorkflowId(), false) + spoolQueue, syncMatchQueue, _, taskDispatchRevisionNumber, targetVersion, err := pm.getPhysicalQueuesForAdd(ctx, directive, params.forwardInfo, params.taskInfo.GetRunId(), params.taskInfo.GetWorkflowId(), false) if err != nil { return "", false, err } - syncMatchTask := newInternalTaskForSyncMatch(params.taskInfo, params.forwardInfo, taskDispatchRevisionNumber) + syncMatchTask := newInternalTaskForSyncMatch(params.taskInfo, params.forwardInfo, taskDispatchRevisionNumber, targetVersion) pm.config.setDefaultPriority(syncMatchTask) if spoolQueue != nil && spoolQueue.QueueKey().Version().BuildId() != syncMatchQueue.QueueKey().Version().BuildId() { // Task is not forwarded and build ID is different on the two queues -> redirect rule is being applied. @@ -410,7 +411,7 @@ func (pm *taskQueuePartitionManagerImpl) ProcessSpooledTask( } // Redirect and re-resolve if we're blocked in matcher and user data changes. for { - newBacklogQueue, syncMatchQueue, userDataChanged, taskDispatchRevisionNumber, err := pm.getPhysicalQueuesForAdd(ctx, + newBacklogQueue, syncMatchQueue, userDataChanged, taskDispatchRevisionNumber, targetVersion, err := pm.getPhysicalQueuesForAdd(ctx, directive, nil, taskInfo.GetRunId(), @@ -420,6 +421,8 @@ func (pm *taskQueuePartitionManagerImpl) ProcessSpooledTask( return err } + task.targetWorkerDeploymentVersion = targetVersion + // Update the task dispatch revision number on the task since the routingConfig of the partition // may have changed after the task was spooled. task.taskDispatchRevisionNumber = taskDispatchRevisionNumber @@ -469,7 +472,7 @@ func (pm *taskQueuePartitionManagerImpl) AddSpooledTask( // construct directive based on the build ID of the spool queue directive = worker_versioning.MakeBuildIdDirective(assignedBuildId) } - newBacklogQueue, syncMatchQueue, _, taskDispatchRevisionNumber, err := pm.getPhysicalQueuesForAdd( + newBacklogQueue, syncMatchQueue, _, taskDispatchRevisionNumber, targetVersion, err := pm.getPhysicalQueuesForAdd( ctx, directive, nil, @@ -481,6 +484,8 @@ func (pm *taskQueuePartitionManagerImpl) AddSpooledTask( return err } + task.targetWorkerDeploymentVersion = targetVersion + // Update the task dispatch revision number on the task since the routingConfig of the partition // may have changed after the task was spooled. task.taskDispatchRevisionNumber = taskDispatchRevisionNumber @@ -520,7 +525,7 @@ func (pm *taskQueuePartitionManagerImpl) DispatchQueryTask( request *matchingservice.QueryWorkflowRequest, ) (*matchingservice.QueryWorkflowResponse, error) { reredirectTask: - _, syncMatchQueue, _, _, err := pm.getPhysicalQueuesForAdd(ctx, + _, syncMatchQueue, _, _, _, err := pm.getPhysicalQueuesForAdd(ctx, request.VersionDirective, // We do not pass forwardInfo because we want the parent partition to make fresh versioning decision. Note that // forwarded Query/Nexus task requests do not expire rapidly in contrast to forwarded activity/workflow tasks @@ -555,7 +560,7 @@ func (pm *taskQueuePartitionManagerImpl) DispatchNexusTask( request *matchingservice.DispatchNexusTaskRequest, ) (*matchingservice.DispatchNexusTaskResponse, error) { reredirectTask: - _, syncMatchQueue, _, _, err := pm.getPhysicalQueuesForAdd(ctx, + _, syncMatchQueue, _, _, _, err := pm.getPhysicalQueuesForAdd(ctx, worker_versioning.MakeUseAssignmentRulesDirective(), // We do not pass forwardInfo because we want the parent partition to make fresh versioning decision. Note that // forwarded Query/Nexus task requests do not expire rapidly in contrast to forwarded activity/workflow tasks @@ -1008,7 +1013,14 @@ func (pm *taskQueuePartitionManagerImpl) getPhysicalQueuesForAdd( runId string, workflowId string, isQuery bool, -) (spoolQueue physicalTaskQueueManager, syncMatchQueue physicalTaskQueueManager, userDataChanged <-chan struct{}, rcRevisionNumber int64, err error) { +) ( + spoolQueue physicalTaskQueueManager, + syncMatchQueue physicalTaskQueueManager, + userDataChanged <-chan struct{}, + rcRevisionNumber int64, + targetVersion *deploymentspb.WorkerDeploymentVersion, + err error, +) { // Note: Revision number mechanics are only involved if the dynamic config, UseRevisionNumberForWorkerVersioning, is enabled. // Represents the revision number used by the task and is max(taskDirectiveRevisionNumber, routingConfigRevisionNumber) for the task. var taskDispatchRevisionNumber, targetDeploymentRevisionNumber int64 @@ -1018,26 +1030,36 @@ func (pm *taskQueuePartitionManagerImpl) getPhysicalQueuesForAdd( perTypeUserData, userDataChanged, err := pm.getPerTypeUserData() if err != nil { - return nil, nil, nil, 0, err + return nil, nil, nil, 0, nil, err } deploymentData := perTypeUserData.GetDeploymentData() taskDirectiveRevisionNumber := directive.GetRevisionNumber() - if wfBehavior == enumspb.VERSIONING_BEHAVIOR_PINNED { + var current, ramping, targetDeploymentVersion *deploymentspb.WorkerDeploymentVersion + var currentRevisionNumber, rampingRevisionNumber int64 + var rampingPercentage float32 + var targetDeployment *deploymentpb.Deployment //nolint:staticcheck // SA1019: [cleanup-wv-3.1] + if wfBehavior != enumspb.VERSIONING_BEHAVIOR_PINNED { + current, currentRevisionNumber, _, ramping, _, rampingPercentage, rampingRevisionNumber, _ = worker_versioning.CalculateTaskQueueVersioningInfo(deploymentData) + targetDeploymentVersion, targetDeploymentRevisionNumber = worker_versioning.FindTargetDeploymentVersionAndRevisionNumberForWorkflowID(current, currentRevisionNumber, ramping, rampingPercentage, rampingRevisionNumber, workflowId) + targetDeployment = worker_versioning.DeploymentFromDeploymentVersion(targetDeploymentVersion) + } + + if worker_versioning.BehaviorIsPinning(wfBehavior) { if pm.partition.Kind() == enumspb.TASK_QUEUE_KIND_STICKY { // TODO (shahab): we can verify the passed deployment matches the last poller's deployment - return pm.defaultQueue, pm.defaultQueue, userDataChanged, 0, nil + return pm.defaultQueue, pm.defaultQueue, userDataChanged, 0, targetDeploymentVersion, nil } err = worker_versioning.ValidateDeployment(deployment) if err != nil { - return nil, nil, nil, 0, err + return nil, nil, nil, 0, nil, err } // Preventing Query tasks from being dispatched to a drained version with no workers if isQuery { if err := pm.checkQueryBlackholed(deploymentData, deployment); err != nil { - return nil, nil, nil, 0, err + return nil, nil, nil, 0, nil, err } } @@ -1056,34 +1078,30 @@ func (pm *taskQueuePartitionManagerImpl) getPhysicalQueuesForAdd( if !isIndependentPinnedActivity { pinnedQueue, err := pm.getVersionedQueue(ctx, "", "", deployment, true) if err != nil { - return nil, nil, nil, 0, err // TODO (Shivam): Please add the comment in the proto to explain that pinned tasks and sticky tasks get 0 for the rev number. + return nil, nil, nil, 0, nil, err // TODO (Shivam): Please add the comment in the proto to explain that pinned tasks and sticky tasks get 0 for the rev number. } if forwardInfo == nil { // Task is not forwarded, so it can be spooled if sync match fails. // Spool queue and sync match queue is the same for pinned workflows. - return pinnedQueue, pinnedQueue, userDataChanged, 0, nil + return pinnedQueue, pinnedQueue, userDataChanged, 0, targetDeploymentVersion, nil } else { // Forwarded from child partition - only do sync match. - return nil, pinnedQueue, userDataChanged, 0, nil + return nil, pinnedQueue, userDataChanged, 0, targetDeploymentVersion, nil } } } - current, currentRevisionNumber, _, ramping, _, rampingPercentage, rampingRevisionNumber, _ := worker_versioning.CalculateTaskQueueVersioningInfo(deploymentData) - targetDeploymentVersion, targetDeploymentRevisionNumber := worker_versioning.FindTargetDeploymentVersionAndRevisionNumberForWorkflowID(current, currentRevisionNumber, ramping, rampingPercentage, rampingRevisionNumber, workflowId) - targetDeployment := worker_versioning.DeploymentFromDeploymentVersion(targetDeploymentVersion) - var targetDeploymentQueue physicalTaskQueueManager if directive.GetAssignedBuildId() == "" && targetDeployment != nil { if pm.partition.Kind() == enumspb.TASK_QUEUE_KIND_STICKY { if !deployment.Equal(targetDeployment) { // Current deployment has changed, so the workflow should move to a normal queue to // get redirected to the new deployment. - return nil, nil, nil, 0, serviceerrors.NewStickyWorkerUnavailable() + return nil, nil, nil, 0, nil, serviceerrors.NewStickyWorkerUnavailable() } // TODO (shahab): we can verify the passed deployment matches the last poller's deployment - return pm.defaultQueue, pm.defaultQueue, userDataChanged, 0, nil + return pm.defaultQueue, pm.defaultQueue, userDataChanged, 0, targetDeploymentVersion, nil } var err error @@ -1094,10 +1112,10 @@ func (pm *taskQueuePartitionManagerImpl) getPhysicalQueuesForAdd( if forwardInfo == nil { // Task is not forwarded, so it can be spooled if sync match fails. // Unpinned tasks are spooled in default queue - return pm.defaultQueue, targetDeploymentQueue, userDataChanged, taskDispatchRevisionNumber, err + return pm.defaultQueue, targetDeploymentQueue, userDataChanged, taskDispatchRevisionNumber, targetDeploymentVersion, err } else { // Forwarded from child partition - only do sync match. - return nil, targetDeploymentQueue, userDataChanged, taskDispatchRevisionNumber, err + return nil, targetDeploymentQueue, userDataChanged, taskDispatchRevisionNumber, targetDeploymentVersion, err } } @@ -1115,18 +1133,18 @@ func (pm *taskQueuePartitionManagerImpl) getPhysicalQueuesForAdd( true, ) } - return nil, syncMatchQueue, nil, taskDispatchRevisionNumber, err + return nil, syncMatchQueue, nil, taskDispatchRevisionNumber, targetDeploymentVersion, err } if directive.GetBuildId() == nil { // The task belongs to an unversioned execution. Keep using unversioned. But also return // userDataChanged so if current deployment is set, the task redirects to that deployment. - return pm.defaultQueue, pm.defaultQueue, userDataChanged, taskDispatchRevisionNumber, nil + return pm.defaultQueue, pm.defaultQueue, userDataChanged, taskDispatchRevisionNumber, targetDeploymentVersion, nil } userData, userDataChanged, err := pm.userDataManager.GetUserData() if err != nil { - return nil, nil, nil, 0, err + return nil, nil, nil, 0, nil, err } data := userData.GetData().GetVersioningData() @@ -1142,7 +1160,7 @@ func (pm *taskQueuePartitionManagerImpl) getPhysicalQueuesForAdd( if buildId == "" { versionSet, err = pm.getVersionSetForAdd(directive, data) if err != nil { - return nil, nil, nil, 0, err + return nil, nil, nil, 0, nil, err } } case *taskqueuespb.TaskVersionDirective_AssignedBuildId: @@ -1152,7 +1170,7 @@ func (pm *taskQueuePartitionManagerImpl) getPhysicalQueuesForAdd( if len(data.GetVersionSets()) > 0 { versionSet, err = pm.getVersionSetForAdd(directive, data) if err != nil { - return nil, nil, nil, 0, err + return nil, nil, nil, 0, nil, err } } if versionSet == "" { @@ -1172,36 +1190,36 @@ func (pm *taskQueuePartitionManagerImpl) getPhysicalQueuesForAdd( // TODO: [cleanup-old-wv] _, err = checkVersionForStickyAdd(data, directive.GetAssignedBuildId()) if err != nil { - return nil, nil, nil, 0, err + return nil, nil, nil, 0, nil, err } if buildId != redirectBuildId { // redirect rule added for buildId, kick task back to normal queue // TODO (shahab): support V3 in here - return nil, nil, nil, 0, serviceerrors.NewStickyWorkerUnavailable() + return nil, nil, nil, 0, nil, serviceerrors.NewStickyWorkerUnavailable() } // sticky queues only use default queue - return pm.defaultQueue, pm.defaultQueue, userDataChanged, 0, nil + return pm.defaultQueue, pm.defaultQueue, userDataChanged, 0, targetDeploymentVersion, nil } if versionSet != "" { spoolQueue = pm.defaultQueue syncMatchQueue, err = pm.getVersionedQueue(ctx, versionSet, "", nil, true) if err != nil { - return nil, nil, nil, 0, err + return nil, nil, nil, 0, nil, err } } else { syncMatchQueue, err = pm.getPhysicalQueue(ctx, redirectBuildId, nil) if err != nil { - return nil, nil, nil, 0, err + return nil, nil, nil, 0, nil, err } // redirect rules are not applied when spooling a task. They'll be applied when dispatching the spool task. spoolQueue, err = pm.getPhysicalQueue(ctx, buildId, nil) if err != nil { - return nil, nil, nil, 0, err + return nil, nil, nil, 0, nil, err } } - return spoolQueue, syncMatchQueue, userDataChanged, taskDispatchRevisionNumber, err + return spoolQueue, syncMatchQueue, userDataChanged, taskDispatchRevisionNumber, targetDeploymentVersion, err } // chooseTargetQueueByFlag picks the target queue and dispatch revision number. From a31a1a3c48ebba8de34349bf452a7ce6a30ade45 Mon Sep 17 00:00:00 2001 From: Carly de Frondeville Date: Mon, 1 Dec 2025 02:47:45 -0800 Subject: [PATCH 06/19] fill in suggest-continue-as-new-reasons --- go.mod | 2 +- go.sum | 4 ++-- .../workflow/workflow_task_state_machine.go | 23 +++++++++++++------ 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index 1d096cab78..fa7c89b887 100644 --- a/go.mod +++ b/go.mod @@ -58,7 +58,7 @@ require ( go.opentelemetry.io/otel/sdk v1.34.0 go.opentelemetry.io/otel/sdk/metric v1.34.0 go.opentelemetry.io/otel/trace v1.34.0 - go.temporal.io/api v1.58.1-0.20251201035831-15eee82a3d3f + go.temporal.io/api v1.58.1-0.20251201103901-ec40ee69ef92 go.temporal.io/sdk v1.35.0 go.uber.org/fx v1.24.0 go.uber.org/mock v0.6.0 diff --git a/go.sum b/go.sum index b4574de92a..61f757b30a 100644 --- a/go.sum +++ b/go.sum @@ -390,8 +390,8 @@ go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= -go.temporal.io/api v1.58.1-0.20251201035831-15eee82a3d3f h1:UwgqC0xTt7Zvsz8d+6O2DZOkhPvoMLKu8A0iPdHueLs= -go.temporal.io/api v1.58.1-0.20251201035831-15eee82a3d3f/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= +go.temporal.io/api v1.58.1-0.20251201103901-ec40ee69ef92 h1:IQEP8X7gSw4WeX+Uww9bWtuWegrQ/HN0J6BCLtVRp4A= +go.temporal.io/api v1.58.1-0.20251201103901-ec40ee69ef92/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= go.temporal.io/sdk v1.35.0 h1:lRNAQ5As9rLgYa7HBvnmKyzxLcdElTuoFJ0FXM/AsLQ= go.temporal.io/sdk v1.35.0/go.mod h1:1q5MuLc2MEJ4lneZTHJzpVebW2oZnyxoIOWX3oFVebw= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= diff --git a/service/history/workflow/workflow_task_state_machine.go b/service/history/workflow/workflow_task_state_machine.go index 7a79018be5..0736ae491e 100644 --- a/service/history/workflow/workflow_task_state_machine.go +++ b/service/history/workflow/workflow_task_state_machine.go @@ -474,15 +474,18 @@ func (m *workflowTaskStateMachine) AddWorkflowTaskStartedEvent( // events. That's okay, it doesn't have to be 100% accurate. It just has to be kept // consistent between the started event in history and the event that was sent to the SDK // that resulted in the successful completion. - suggestContinueAsNew, historySizeBytes := m.getHistorySizeInfo() - if updateReg != nil { - suggestContinueAsNew = cmp.Or(suggestContinueAsNew, updateReg.SuggestContinueAsNew()) + historySizeBytes, suggestContinueAsNewReasons := m.getHistorySizeInfo() + suggestContinueAsNew := len(suggestContinueAsNewReasons) > 0 + if updateReg != nil && updateReg.SuggestContinueAsNew() { + suggestContinueAsNew = cmp.Or(suggestContinueAsNew, true) + suggestContinueAsNewReasons = append(suggestContinueAsNewReasons, enumspb.SUGGEST_CONTINUE_AS_NEW_REASON_TOO_MANY_UPDATES) } if m.ms.GetEffectiveVersioningBehavior() == enumspb.VERSIONING_BEHAVIOR_PINNED_UNTIL_CONTINUE_AS_NEW && targetDeploymentVersion != nil { if currentDeploymentVersion := m.ms.GetEffectiveDeployment(); currentDeploymentVersion.BuildId != targetDeploymentVersion.BuildId || currentDeploymentVersion.SeriesName != targetDeploymentVersion.DeploymentName { suggestContinueAsNew = cmp.Or(suggestContinueAsNew, true) + suggestContinueAsNewReasons = append(suggestContinueAsNewReasons, enumspb.SUGGEST_CONTINUE_AS_NEW_REASON_TARGET_WORKER_DEPLOYMENT_VERSION_CHANGED) } } @@ -1327,10 +1330,11 @@ func (m *workflowTaskStateMachine) getStartToCloseTimeout( return durationpb.New(startToCloseTimeout) } -func (m *workflowTaskStateMachine) getHistorySizeInfo() (bool, int64) { +func (m *workflowTaskStateMachine) getHistorySizeInfo() (int64, []enumspb.SuggestContinueAsNewReason) { + var reasons []enumspb.SuggestContinueAsNewReason stats := m.ms.GetExecutionInfo().ExecutionStats if stats == nil { - return false, 0 + return 0, reasons } // This only includes events that have actually been written to persistence, so it won't // include the workflow task started event that we're currently writing. That's okay, it @@ -1343,8 +1347,13 @@ func (m *workflowTaskStateMachine) getHistorySizeInfo() (bool, int64) { namespaceName := m.ms.GetNamespaceEntry().Name().String() sizeLimit := int64(config.HistorySizeSuggestContinueAsNew(namespaceName)) countLimit := int64(config.HistoryCountSuggestContinueAsNew(namespaceName)) - suggestContinueAsNew := historySize >= sizeLimit || historyCount >= countLimit - return suggestContinueAsNew, historySize + if historySize >= sizeLimit { + reasons = append(reasons, enumspb.SUGGEST_CONTINUE_AS_NEW_REASON_HISTORY_SIZE_TOO_LARGE) + } + if historyCount >= countLimit { + reasons = append(reasons, enumspb.SUGGEST_CONTINUE_AS_NEW_REASON_TOO_MANY_HISTORY_EVENTS) + } + return historySize, reasons } func (m *workflowTaskStateMachine) convertSpeculativeWorkflowTaskToNormal() error { From a5affb23c7c75eab39364370319cd3f21d41b40e Mon Sep 17 00:00:00 2001 From: Carly de Frondeville Date: Mon, 1 Dec 2025 02:48:33 -0800 Subject: [PATCH 07/19] add todo --- service/history/workflow/workflow_task_state_machine.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/history/workflow/workflow_task_state_machine.go b/service/history/workflow/workflow_task_state_machine.go index 0736ae491e..e22339aa17 100644 --- a/service/history/workflow/workflow_task_state_machine.go +++ b/service/history/workflow/workflow_task_state_machine.go @@ -519,7 +519,7 @@ func (m *workflowTaskStateMachine) AddWorkflowTaskStartedEvent( // Create WorkflowTaskStartedEvent only if WorkflowTaskScheduledEvent was created. // (it wasn't created for transient/speculative WT). var startedEvent *historypb.HistoryEvent - if workflowTaskScheduledEventCreated { + if workflowTaskScheduledEventCreated { // TODO(carlydf): put reasons into history event startedEvent = m.ms.hBuilder.AddWorkflowTaskStartedEvent( scheduledEventID, requestID, From 4fe98c42ae14b3d68ee227d2f4ea53de592bcfe9 Mon Sep 17 00:00:00 2001 From: Carly de Frondeville Date: Tue, 9 Dec 2025 15:29:31 -0800 Subject: [PATCH 08/19] use CaN-option for trampolining instead of new behavior --- common/worker_versioning/worker_versioning.go | 21 ++-------- go.mod | 2 +- go.sum | 4 +- .../history/api/updateworkflowoptions/api.go | 17 +------- .../transfer_queue_active_task_executor.go | 10 ++--- .../history/workflow/mutable_state_impl.go | 23 +++++------ service/history/workflow/retry.go | 40 +++++-------------- service/history/workflow/util.go | 25 +----------- .../workflow/workflow_task_state_machine.go | 2 +- .../matching/task_queue_partition_manager.go | 2 +- 10 files changed, 35 insertions(+), 111 deletions(-) diff --git a/common/worker_versioning/worker_versioning.go b/common/worker_versioning/worker_versioning.go index b53647c3d2..e64d8a44f8 100644 --- a/common/worker_versioning/worker_versioning.go +++ b/common/worker_versioning/worker_versioning.go @@ -466,18 +466,10 @@ func ValidateDeploymentVersionStringV31(version string) (*deploymentspb.WorkerDe return v, nil } -// OverrideIsPinned is true if the override behavior is any of the Pinned override behaviors. func OverrideIsPinned(override *workflowpb.VersioningOverride) bool { //nolint:staticcheck // SA1019: worker versioning v0.31 and v0.30 return override.GetBehavior() == enumspb.VERSIONING_BEHAVIOR_PINNED || - override.GetPinned().GetBehavior() == workflowpb.VersioningOverride_PINNED_OVERRIDE_BEHAVIOR_UNSPECIFIED -} - -func BehaviorIsPinning(behavior enumspb.VersioningBehavior) bool { - if behavior == enumspb.VERSIONING_BEHAVIOR_PINNED || behavior == enumspb.VERSIONING_BEHAVIOR_PINNED_UNTIL_CONTINUE_AS_NEW { - return true - } - return false + override.GetPinned().GetBehavior() == workflowpb.VersioningOverride_PINNED_OVERRIDE_BEHAVIOR_PINNED } func GetOverridePinnedVersion(override *workflowpb.VersioningOverride) *deploymentpb.WorkerDeploymentVersion { @@ -510,17 +502,12 @@ func ValidateVersioningOverride(override *workflowpb.VersioningOverride) error { if override.GetAutoUpgrade() { // v0.32 return nil } else if p := override.GetPinned(); p != nil { - switch p.GetBehavior() { - case workflowpb.VersioningOverride_PINNED_OVERRIDE_BEHAVIOR_UNSPECIFIED: - return serviceerror.NewInvalidArgument("must specify pinned override behavior if override is pinned.") - case workflowpb.VersioningOverride_PINNED_OVERRIDE_BEHAVIOR_KEEP_IF_PINNING: - if p.GetIfNotPinning() == workflowpb.VersioningOverride_NON_PINNING_POLICY_UNSPECIFIED { - return serviceerror.NewInvalidArgument("must specify non-pinning policy if behavior is 'KEEP_IF_PINNING'") - } + if p.GetVersion() == nil { + return serviceerror.NewInvalidArgument("must provide version if override is pinned.") } if p.GetBehavior() == workflowpb.VersioningOverride_PINNED_OVERRIDE_BEHAVIOR_UNSPECIFIED { + return serviceerror.NewInvalidArgument("must specify pinned override behavior if override is pinned.") } - return nil } diff --git a/go.mod b/go.mod index fa7c89b887..956361572d 100644 --- a/go.mod +++ b/go.mod @@ -58,7 +58,7 @@ require ( go.opentelemetry.io/otel/sdk v1.34.0 go.opentelemetry.io/otel/sdk/metric v1.34.0 go.opentelemetry.io/otel/trace v1.34.0 - go.temporal.io/api v1.58.1-0.20251201103901-ec40ee69ef92 + go.temporal.io/api v1.58.1-0.20251209231134-96c102d384da go.temporal.io/sdk v1.35.0 go.uber.org/fx v1.24.0 go.uber.org/mock v0.6.0 diff --git a/go.sum b/go.sum index 61f757b30a..cc4879cf29 100644 --- a/go.sum +++ b/go.sum @@ -390,8 +390,8 @@ go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= -go.temporal.io/api v1.58.1-0.20251201103901-ec40ee69ef92 h1:IQEP8X7gSw4WeX+Uww9bWtuWegrQ/HN0J6BCLtVRp4A= -go.temporal.io/api v1.58.1-0.20251201103901-ec40ee69ef92/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= +go.temporal.io/api v1.58.1-0.20251209231134-96c102d384da h1:PmSa202VNl0WTn4OCBsqGw31KdW7UpAJqBfxfUh/sec= +go.temporal.io/api v1.58.1-0.20251209231134-96c102d384da/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= go.temporal.io/sdk v1.35.0 h1:lRNAQ5As9rLgYa7HBvnmKyzxLcdElTuoFJ0FXM/AsLQ= go.temporal.io/sdk v1.35.0/go.mod h1:1q5MuLc2MEJ4lneZTHJzpVebW2oZnyxoIOWX3oFVebw= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= diff --git a/service/history/api/updateworkflowoptions/api.go b/service/history/api/updateworkflowoptions/api.go index 0cbf47cbd6..c05155ee09 100644 --- a/service/history/api/updateworkflowoptions/api.go +++ b/service/history/api/updateworkflowoptions/api.go @@ -3,6 +3,7 @@ package updateworkflowoptions import ( "context" + enumspb "go.temporal.io/api/enums/v1" "go.temporal.io/api/serviceerror" workflowpb "go.temporal.io/api/workflow/v1" "go.temporal.io/server/api/historyservice/v1" @@ -107,26 +108,12 @@ func MergeAndApply( return mergedOpts, false, nil } - // Reject 'keep_if_pinning' override if base behavior is not a Pinning Behavior (Pinned or PinnedUntilContinueAsNew) - if mergedOpts.GetVersioningOverride().GetPinned().GetBehavior() == workflowpb.VersioningOverride_PINNED_OVERRIDE_BEHAVIOR_KEEP_IF_PINNING && - !worker_versioning.BehaviorIsPinning(ms.GetExecutionInfo().GetVersioningInfo().GetBehavior()) { - nonPinningPolicy := mergedOpts.GetVersioningOverride().GetPinned().GetIfNotPinning() - if nonPinningPolicy == workflowpb.VersioningOverride_NON_PINNING_POLICY_REJECT { - return nil, - false, - serviceerror.NewFailedPreconditionf("rejecting 'keep_if_pinning' override because 'non_pinning_policy' is 'REJECT' and behavior for workflow with id %v is '%s'", - ms.GetExecutionInfo().GetWorkflowId(), - ms.GetExecutionInfo().GetVersioningInfo().GetBehavior().String(), - ) - } - } - // If the requested override is pinned and omitted optional pinned version, fill in the current pinned version if it exists, // or error if no pinned version exists. if mergedOpts.GetVersioningOverride().GetPinned().GetBehavior() != workflowpb.VersioningOverride_PINNED_OVERRIDE_BEHAVIOR_UNSPECIFIED && mergedOpts.GetVersioningOverride().GetPinned().GetVersion() == nil { currentVersion := worker_versioning.ExternalWorkerDeploymentVersionFromDeployment(workflow.GetEffectiveDeployment(ms.GetExecutionInfo().GetVersioningInfo())) - if worker_versioning.BehaviorIsPinning(workflow.GetEffectiveVersioningBehavior(ms.GetExecutionInfo().GetVersioningInfo())) { + if workflow.GetEffectiveVersioningBehavior(ms.GetExecutionInfo().GetVersioningInfo()) == enumspb.VERSIONING_BEHAVIOR_PINNED { mergedOpts.GetVersioningOverride().GetPinned().Version = currentVersion } else { return nil, false, serviceerror.NewFailedPreconditionf("must specify a specific pinned override version because workflow with id %v has behavior %s and is not yet pinned to any version", diff --git a/service/history/transfer_queue_active_task_executor.go b/service/history/transfer_queue_active_task_executor.go index f1167db7c8..b25a39a351 100644 --- a/service/history/transfer_queue_active_task_executor.go +++ b/service/history/transfer_queue_active_task_executor.go @@ -914,10 +914,9 @@ func (t *transferQueueActiveTaskExecutor) processStartChildExecution( // the first matching task-queue-in-version check. newTQInPinnedVersion := false - // Child of a parent with a Pinning Behavior (Pinned or PinnedUntilContinueAsNew) will inherit the parent's version - // if the Child's Task Queue belongs to that version. + // Child of a parent with Pinned behavior will inherit the parent's version if the Child's Task Queue belongs to that version. var inheritedPinnedVersion *deploymentpb.WorkerDeploymentVersion - if worker_versioning.BehaviorIsPinning(mutableState.GetEffectiveVersioningBehavior()) { + if mutableState.GetEffectiveVersioningBehavior() == enumspb.VERSIONING_BEHAVIOR_PINNED { inheritedPinnedVersion = worker_versioning.ExternalWorkerDeploymentVersionFromDeployment(mutableState.GetEffectiveDeployment()) newTQ := attributes.GetTaskQueue().GetName() if attributes.GetNamespaceId() != mutableState.GetExecutionInfo().GetNamespaceId() { // don't inherit pinned version if child is in a different namespace @@ -934,11 +933,10 @@ func (t *transferQueueActiveTaskExecutor) processStartChildExecution( } // Pinned override is inherited if Task Queue of new run is compatible with the override version. - // Effective versioning behavior of the workflow must be a Pinning Behavior (Pinned or PinnedUntilContinueAsNew) - // to inherit the pinned override version. + // Effective versioning behavior of the workflow must be Pinned to inherit the pinned override version. var inheritedPinnedOverride *workflowpb.VersioningOverride if o := mutableState.GetExecutionInfo().GetVersioningInfo().GetVersioningOverride(); worker_versioning.OverrideIsPinned(o) && - worker_versioning.BehaviorIsPinning(workflow.GetEffectiveVersioningBehavior(mutableState.GetExecutionInfo().GetVersioningInfo())) { + workflow.GetEffectiveVersioningBehavior(mutableState.GetExecutionInfo().GetVersioningInfo()) == enumspb.VERSIONING_BEHAVIOR_PINNED { inheritedPinnedOverride = o newTQ := attributes.GetTaskQueue().GetName() if newTQ != mutableState.GetExecutionInfo().GetTaskQueue() && !newTQInPinnedVersion || diff --git a/service/history/workflow/mutable_state_impl.go b/service/history/workflow/mutable_state_impl.go index 62b830368b..0537a10845 100644 --- a/service/history/workflow/mutable_state_impl.go +++ b/service/history/workflow/mutable_state_impl.go @@ -2445,12 +2445,13 @@ func (ms *MutableStateImpl) addWorkflowExecutionStartedEventForContinueAsNew( // the first matching task-queue-in-version check. newTQInPinnedVersion := false - // New run initiated by workflow ContinueAsNew of a Pinned run, will inherit the previous run's version if the - // new run's Task Queue belongs to that version. - // If the initiating workflow is PinnedUntilContinueAsNew, the new run will start as AutoUpgrade in the first task - // and then assume the SDK-sent behavior on first workflow task completion. + // By default, the new run initiated by workflow ContinueAsNew of a Pinned run, will inherit the previous run's + // version if the new run's Task Queue belongs to that version. + // If the continue-as-new command says to use InitialVersioningBehavior AutoUpgrade, the new run will start as + // AutoUpgrade in the first task and then assume the SDK-sent behavior on first workflow task completion. var inheritedPinnedVersion *deploymentpb.WorkerDeploymentVersion - if previousExecutionState.GetEffectiveVersioningBehavior() == enumspb.VERSIONING_BEHAVIOR_PINNED { + if previousExecutionState.GetEffectiveVersioningBehavior() == enumspb.VERSIONING_BEHAVIOR_PINNED && + command.GetInitialVersioningBehavior() != enumspb.CONTINUE_AS_NEW_VERSIONING_BEHAVIOR_AUTO_UPGRADE { inheritedPinnedVersion = worker_versioning.ExternalWorkerDeploymentVersionFromDeployment(previousExecutionState.GetEffectiveDeployment()) newTQ := command.GetTaskQueue().GetName() if newTQ != previousExecutionInfo.GetTaskQueue() { @@ -2465,8 +2466,6 @@ func (ms *MutableStateImpl) addWorkflowExecutionStartedEventForContinueAsNew( } // Pinned override is inherited if Task Queue of new run is compatible with the override version. - // For continue-as-new, the effective versioning behavior of the workflow must be strictly Pinned (not - // PinnedUntilContinueAsNew) to inherit the pinned override version. var pinnedOverride *workflowpb.VersioningOverride if o := previousExecutionInfo.GetVersioningInfo().GetVersioningOverride(); worker_versioning.OverrideIsPinned(o) && GetEffectiveVersioningBehavior(previousExecutionInfo.GetVersioningInfo()) == enumspb.VERSIONING_BEHAVIOR_PINNED { @@ -2477,13 +2476,13 @@ func (ms *MutableStateImpl) addWorkflowExecutionStartedEventForContinueAsNew( } } - // New run initiated by ContinueAsNew of an AUTO_UPGRADE or PINNED_UNTIL_CONTINUE_AS_NEW workflow execution will - // inherit the previous run's deployment version and revision number iff the new run's Task Queue belongs to the - // source deployment version. + // New run initiated by ContinueAsNew of an AUTO_UPGRADE workflow execution will inherit the previous run's + // deployment version and revision number iff the new run's Task Queue belongs to the source deployment version. var sourceDeploymentVersion *deploymentpb.WorkerDeploymentVersion var sourceDeploymentRevisionNumber int64 if previousExecutionState.GetEffectiveVersioningBehavior() == enumspb.VERSIONING_BEHAVIOR_AUTO_UPGRADE || - previousExecutionState.GetEffectiveVersioningBehavior() == enumspb.VERSIONING_BEHAVIOR_PINNED_UNTIL_CONTINUE_AS_NEW { + (previousExecutionState.GetEffectiveVersioningBehavior() == enumspb.VERSIONING_BEHAVIOR_PINNED && + command.GetInitialVersioningBehavior() == enumspb.CONTINUE_AS_NEW_VERSIONING_BEHAVIOR_AUTO_UPGRADE) { sourceDeploymentVersion = worker_versioning.ExternalWorkerDeploymentVersionFromDeployment(previousExecutionState.GetEffectiveDeployment()) sourceDeploymentRevisionNumber = previousExecutionState.GetVersioningRevisionNumber() @@ -2875,7 +2874,6 @@ func (ms *MutableStateImpl) ApplyWorkflowExecutionStartedEvent( ms.executionInfo.VersioningInfo = &workflowpb.WorkflowExecutionVersioningInfo{} } ms.executionInfo.VersioningInfo.DeploymentVersion = event.GetInheritedPinnedVersion() - // TODO(carlydf): Can't just assume this anymore -- the behavior might need to be PINNED_UNTIL_CONTINUE_AS_NEW, could add a new field called InheritedPinningBehavior. ms.executionInfo.VersioningInfo.Behavior = enumspb.VERSIONING_BEHAVIOR_PINNED } @@ -2917,7 +2915,6 @@ func (ms *MutableStateImpl) ApplyWorkflowExecutionStartedEvent( ms.executionInfo.VersioningInfo = &workflowpb.WorkflowExecutionVersioningInfo{} } ms.executionInfo.VersioningInfo.DeploymentVersion = event.GetInheritedAutoUpgradeInfo().GetSourceDeploymentVersion() - // TODO(carlydf): Decide whether it's ok for continued-as-new workflows whose previous workflow had PINNED_UNTIL_CONTINUE_AS_NEW behavior to start with AUTO_UPGRADE behavior, or if they should instead start with blank or PINNED_UNTIL_CONTINUE_AS_NEW behavior. // Assume AutoUpgrade behavior for the first workflow task. ms.executionInfo.VersioningInfo.Behavior = enumspb.VERSIONING_BEHAVIOR_AUTO_UPGRADE } diff --git a/service/history/workflow/retry.go b/service/history/workflow/retry.go index 0870393204..fe2970ba48 100644 --- a/service/history/workflow/retry.go +++ b/service/history/workflow/retry.go @@ -243,44 +243,23 @@ func SetupNewWorkflowForRetryOrCron( } } - // Retries and Cron workflows initiated by workflows with a Pinning Behavior (Pinned or PinnedUntilContinueAsNew) will inherit - // the initiator's version if the resulting workflow's Task Queue belongs to that version. var pinnedOverride *workflowpb.VersioningOverride - if o := previousExecutionInfo.GetVersioningInfo().GetVersioningOverride(); worker_versioning.OverrideIsPinned(o) && - worker_versioning.BehaviorIsPinning(GetEffectiveVersioningBehavior(previousExecutionInfo.GetVersioningInfo())) { + if o := previousExecutionInfo.GetVersioningInfo().GetVersioningOverride(); worker_versioning.OverrideIsPinned(o) { pinnedOverride = o // retries and crons always go to the same task queue, so no need to check if override version is in new task queue } - // New run initiated by workflow Cron will never inherit a non-override version. + // New run initiated by workflow Cron will never inherit. // - // New run initiated by workflow Retry will only inherit if the retried run is effectively Pinned or PinnedUntilContinueAsNew - // at the time of retry, and the retried run inherited a pinned version when it started (ie. it is a child of a parent - // with Pinned or PinnedUntilContinueAsNew behavior, or a CaN of run with Pinned behavior, and is running on a Task - // Queue in the inherited version). + // New run initiated by workflow Retry will only inherit if the retried run is effectively pinned at the time + // of retry, and the retried run inherited a pinned version when it started (ie. it is a child of a pinned + // parent, or a CaN of a pinned run, and is running on a Task Queue in the inherited version). var inheritedPinnedVersion *deploymentpb.WorkerDeploymentVersion - // If the previous run had an AutoUpgrade behavior, we pass down the source deployment version and revision number to the new run. - // Note: We only pass down one of inheritedPinnedVersion or inheritedAutoUpgradeInfo, but not both! - var inheritedAutoUpgradeInfo *deploymentpb.InheritedAutoUpgradeInfo - - if initiator == enumspb.CONTINUE_AS_NEW_INITIATOR_RETRY { + if initiator == enumspb.CONTINUE_AS_NEW_INITIATOR_RETRY && + GetEffectiveVersioningBehavior(previousExecutionInfo.GetVersioningInfo()) == enumspb.VERSIONING_BEHAVIOR_PINNED && + startAttr.GetInheritedPinnedVersion() != nil { + inheritedPinnedVersion = worker_versioning.ExternalWorkerDeploymentVersionFromDeployment(GetEffectiveDeployment(previousExecutionInfo.GetVersioningInfo())) // retries and crons always go to the same task queue, so no need to check if override version is in new task queue - - if worker_versioning.BehaviorIsPinning(GetEffectiveVersioningBehavior(previousExecutionInfo.GetVersioningInfo())) && - startAttr.GetInheritedPinnedVersion() != nil { - inheritedPinnedVersion = worker_versioning.ExternalWorkerDeploymentVersionFromDeployment(GetEffectiveDeployment(previousExecutionInfo.GetVersioningInfo())) - } else if GetEffectiveVersioningBehavior(previousExecutionInfo.GetVersioningInfo()) == enumspb.VERSIONING_BEHAVIOR_AUTO_UPGRADE { - sourceDeploymentVersion := worker_versioning.ExternalWorkerDeploymentVersionFromDeployment(previousMutableState.GetEffectiveDeployment()) - sourceDeploymentRevisionNumber := previousMutableState.GetVersioningRevisionNumber() - - // Only set inherited auto upgrade info if source deployment version and revision number are not nil. - if sourceDeploymentVersion != nil && sourceDeploymentRevisionNumber != 0 { - inheritedAutoUpgradeInfo = &deploymentpb.InheritedAutoUpgradeInfo{ - SourceDeploymentVersion: sourceDeploymentVersion, - SourceDeploymentRevisionNumber: sourceDeploymentRevisionNumber, - } - } - } } createRequest := &workflowservice.StartWorkflowExecutionRequest{ @@ -334,7 +313,6 @@ func SetupNewWorkflowForRetryOrCron( RootExecutionInfo: rootInfo, InheritedBuildId: startAttr.InheritedBuildId, InheritedPinnedVersion: inheritedPinnedVersion, - InheritedAutoUpgradeInfo: inheritedAutoUpgradeInfo, } workflowTimeoutTime := timestamp.TimeValue(previousExecutionInfo.WorkflowExecutionExpirationTime) if !workflowTimeoutTime.IsZero() { diff --git a/service/history/workflow/util.go b/service/history/workflow/util.go index de36899374..d87470ee60 100644 --- a/service/history/workflow/util.go +++ b/service/history/workflow/util.go @@ -249,30 +249,7 @@ func GetEffectiveVersioningBehavior(versioningInfo *workflowpb.WorkflowExecution if override.GetAutoUpgrade() { return enumspb.VERSIONING_BEHAVIOR_AUTO_UPGRADE } - switch override.GetPinned().GetBehavior() { - case workflowpb.VersioningOverride_PINNED_OVERRIDE_BEHAVIOR_PINNED, workflowpb.VersioningOverride_PINNED_OVERRIDE_BEHAVIOR_UNSPECIFIED: - return enumspb.VERSIONING_BEHAVIOR_PINNED - case workflowpb.VersioningOverride_PINNED_OVERRIDE_BEHAVIOR_PINNED_UNTIL_CONTINUE_AS_NEW: - return enumspb.VERSIONING_BEHAVIOR_PINNED_UNTIL_CONTINUE_AS_NEW - case workflowpb.VersioningOverride_PINNED_OVERRIDE_BEHAVIOR_KEEP_IF_PINNING: - if versioningInfo.GetBehavior() == enumspb.VERSIONING_BEHAVIOR_PINNED || - versioningInfo.GetBehavior() == enumspb.VERSIONING_BEHAVIOR_PINNED_UNTIL_CONTINUE_AS_NEW { - return versioningInfo.GetBehavior() - } else { - switch override.GetPinned().GetIfNotPinning() { - case workflowpb.VersioningOverride_NON_PINNING_POLICY_REJECT, workflowpb.VersioningOverride_NON_PINNING_POLICY_UNSPECIFIED: - // this should never happen, but if it does, treat it as ignore - //TODO(carlydf): do an antithesis-compatible assertion here - return versioningInfo.GetBehavior() - case workflowpb.VersioningOverride_NON_PINNING_POLICY_IGNORE: - return versioningInfo.GetBehavior() - case workflowpb.VersioningOverride_NON_PINNING_POLICY_PINNED: - return enumspb.VERSIONING_BEHAVIOR_PINNED - case workflowpb.VersioningOverride_NON_PINNING_POLICY_PINNED_UNTIL_CONTINUE_AS_NEW: - return enumspb.VERSIONING_BEHAVIOR_PINNED_UNTIL_CONTINUE_AS_NEW - } - } - } + return enumspb.VERSIONING_BEHAVIOR_PINNED } return override.GetBehavior() // //nolint:staticcheck // SA1019: worker versioning v0.31 and v0.30 } diff --git a/service/history/workflow/workflow_task_state_machine.go b/service/history/workflow/workflow_task_state_machine.go index e22339aa17..27a709b995 100644 --- a/service/history/workflow/workflow_task_state_machine.go +++ b/service/history/workflow/workflow_task_state_machine.go @@ -481,7 +481,7 @@ func (m *workflowTaskStateMachine) AddWorkflowTaskStartedEvent( suggestContinueAsNewReasons = append(suggestContinueAsNewReasons, enumspb.SUGGEST_CONTINUE_AS_NEW_REASON_TOO_MANY_UPDATES) } - if m.ms.GetEffectiveVersioningBehavior() == enumspb.VERSIONING_BEHAVIOR_PINNED_UNTIL_CONTINUE_AS_NEW && targetDeploymentVersion != nil { + if m.ms.GetEffectiveVersioningBehavior() != enumspb.VERSIONING_BEHAVIOR_UNSPECIFIED && targetDeploymentVersion != nil { if currentDeploymentVersion := m.ms.GetEffectiveDeployment(); currentDeploymentVersion.BuildId != targetDeploymentVersion.BuildId || currentDeploymentVersion.SeriesName != targetDeploymentVersion.DeploymentName { suggestContinueAsNew = cmp.Or(suggestContinueAsNew, true) diff --git a/service/matching/task_queue_partition_manager.go b/service/matching/task_queue_partition_manager.go index d16687e086..48e08fcfee 100644 --- a/service/matching/task_queue_partition_manager.go +++ b/service/matching/task_queue_partition_manager.go @@ -1045,7 +1045,7 @@ func (pm *taskQueuePartitionManagerImpl) getPhysicalQueuesForAdd( targetDeployment = worker_versioning.DeploymentFromDeploymentVersion(targetDeploymentVersion) } - if worker_versioning.BehaviorIsPinning(wfBehavior) { + if wfBehavior == enumspb.VERSIONING_BEHAVIOR_PINNED { if pm.partition.Kind() == enumspb.TASK_QUEUE_KIND_STICKY { // TODO (shahab): we can verify the passed deployment matches the last poller's deployment return pm.defaultQueue, pm.defaultQueue, userDataChanged, 0, targetDeploymentVersion, nil From a691070679e0945962a009c02ea4b1b11486335b Mon Sep 17 00:00:00 2001 From: Carly de Frondeville Date: Tue, 9 Dec 2025 15:58:04 -0800 Subject: [PATCH 09/19] make versioning override version optional in separate PR --- go.mod | 2 +- go.sum | 4 ++-- .../history/api/updateworkflowoptions/api.go | 17 ----------------- 3 files changed, 3 insertions(+), 20 deletions(-) diff --git a/go.mod b/go.mod index 956361572d..56fa2a8bfe 100644 --- a/go.mod +++ b/go.mod @@ -58,7 +58,7 @@ require ( go.opentelemetry.io/otel/sdk v1.34.0 go.opentelemetry.io/otel/sdk/metric v1.34.0 go.opentelemetry.io/otel/trace v1.34.0 - go.temporal.io/api v1.58.1-0.20251209231134-96c102d384da + go.temporal.io/api v1.58.1-0.20251209235529-e917f5407b0e go.temporal.io/sdk v1.35.0 go.uber.org/fx v1.24.0 go.uber.org/mock v0.6.0 diff --git a/go.sum b/go.sum index cc4879cf29..505db9af87 100644 --- a/go.sum +++ b/go.sum @@ -390,8 +390,8 @@ go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= -go.temporal.io/api v1.58.1-0.20251209231134-96c102d384da h1:PmSa202VNl0WTn4OCBsqGw31KdW7UpAJqBfxfUh/sec= -go.temporal.io/api v1.58.1-0.20251209231134-96c102d384da/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= +go.temporal.io/api v1.58.1-0.20251209235529-e917f5407b0e h1:g6xxnENbIdyubpzzEupQoStJWwnR4qP32or9nfylNoM= +go.temporal.io/api v1.58.1-0.20251209235529-e917f5407b0e/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= go.temporal.io/sdk v1.35.0 h1:lRNAQ5As9rLgYa7HBvnmKyzxLcdElTuoFJ0FXM/AsLQ= go.temporal.io/sdk v1.35.0/go.mod h1:1q5MuLc2MEJ4lneZTHJzpVebW2oZnyxoIOWX3oFVebw= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= diff --git a/service/history/api/updateworkflowoptions/api.go b/service/history/api/updateworkflowoptions/api.go index c05155ee09..1f591bde30 100644 --- a/service/history/api/updateworkflowoptions/api.go +++ b/service/history/api/updateworkflowoptions/api.go @@ -3,7 +3,6 @@ package updateworkflowoptions import ( "context" - enumspb "go.temporal.io/api/enums/v1" "go.temporal.io/api/serviceerror" workflowpb "go.temporal.io/api/workflow/v1" "go.temporal.io/server/api/historyservice/v1" @@ -14,7 +13,6 @@ import ( "go.temporal.io/server/service/history/api" "go.temporal.io/server/service/history/consts" historyi "go.temporal.io/server/service/history/interfaces" - "go.temporal.io/server/service/history/workflow" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/fieldmaskpb" ) @@ -108,21 +106,6 @@ func MergeAndApply( return mergedOpts, false, nil } - // If the requested override is pinned and omitted optional pinned version, fill in the current pinned version if it exists, - // or error if no pinned version exists. - if mergedOpts.GetVersioningOverride().GetPinned().GetBehavior() != workflowpb.VersioningOverride_PINNED_OVERRIDE_BEHAVIOR_UNSPECIFIED && - mergedOpts.GetVersioningOverride().GetPinned().GetVersion() == nil { - currentVersion := worker_versioning.ExternalWorkerDeploymentVersionFromDeployment(workflow.GetEffectiveDeployment(ms.GetExecutionInfo().GetVersioningInfo())) - if workflow.GetEffectiveVersioningBehavior(ms.GetExecutionInfo().GetVersioningInfo()) == enumspb.VERSIONING_BEHAVIOR_PINNED { - mergedOpts.GetVersioningOverride().GetPinned().Version = currentVersion - } else { - return nil, false, serviceerror.NewFailedPreconditionf("must specify a specific pinned override version because workflow with id %v has behavior %s and is not yet pinned to any version", - ms.GetExecutionInfo().GetWorkflowId(), - ms.GetExecutionInfo().GetVersioningInfo().GetBehavior().String(), - ) - } - } - unsetOverride := false if mergedOpts.GetVersioningOverride() == nil { unsetOverride = true From 4a9d229622079245db77ee264ae03cff941e2609 Mon Sep 17 00:00:00 2001 From: Carly de Frondeville Date: Tue, 9 Dec 2025 16:46:43 -0800 Subject: [PATCH 10/19] fix todos and reduce diff --- service/frontend/workflow_handler.go | 16 ---------------- service/history/historybuilder/event_factory.go | 16 +++++++++------- .../history/historybuilder/history_builder.go | 2 ++ .../history_builder_categorization_test.go | 2 +- .../historybuilder/history_builder_test.go | 1 + .../transfer_queue_active_task_executor.go | 3 +-- service/history/workflow/mutable_state_impl.go | 6 +++++- .../workflow/workflow_task_state_machine.go | 7 ++++++- service/matching/matching_engine.go | 1 - service/matching/task_queue_partition_manager.go | 2 +- 10 files changed, 26 insertions(+), 30 deletions(-) diff --git a/service/frontend/workflow_handler.go b/service/frontend/workflow_handler.go index 20dd6a3cbd..47a5425b24 100644 --- a/service/frontend/workflow_handler.go +++ b/service/frontend/workflow_handler.go @@ -526,14 +526,6 @@ func (wh *WorkflowHandler) prepareStartWorkflowRequest( return nil, err } - if err := worker_versioning.ValidateVersioningOverride(request.GetVersioningOverride()); err != nil { - return nil, err - } - if request.GetVersioningOverride().GetPinned().GetBehavior() != workflowpb.VersioningOverride_PINNED_OVERRIDE_BEHAVIOR_UNSPECIFIED && - request.GetVersioningOverride().GetPinned().GetVersion() == nil { - return nil, serviceerror.NewInvalidArgument("override version is required to start a workflow with a pinned override") - } - return request, nil } @@ -2082,14 +2074,6 @@ func (wh *WorkflowHandler) SignalWithStartWorkflowExecution(ctx context.Context, return nil, err } - if err := worker_versioning.ValidateVersioningOverride(request.GetVersioningOverride()); err != nil { - return nil, err - } - if request.GetVersioningOverride().GetPinned().GetBehavior() != workflowpb.VersioningOverride_PINNED_OVERRIDE_BEHAVIOR_UNSPECIFIED && - request.GetVersioningOverride().GetPinned().GetVersion() == nil { - return nil, serviceerror.NewInvalidArgument("override version is required to start a workflow with a pinned override") - } - namespaceID, err := wh.namespaceRegistry.GetNamespaceID(namespaceName) if err != nil { return nil, err diff --git a/service/history/historybuilder/event_factory.go b/service/history/historybuilder/event_factory.go index 86365c2ee5..e47f4e44ae 100644 --- a/service/history/historybuilder/event_factory.go +++ b/service/history/historybuilder/event_factory.go @@ -115,17 +115,19 @@ func (b *EventFactory) CreateWorkflowTaskStartedEvent( historySizeBytes int64, versioningStamp *commonpb.WorkerVersionStamp, buildIdRedirectCounter int64, + suggestContinueAsNewReasons []enumspb.SuggestContinueAsNewReason, ) *historypb.HistoryEvent { event := b.createHistoryEvent(enumspb.EVENT_TYPE_WORKFLOW_TASK_STARTED, startTime) event.Attributes = &historypb.HistoryEvent_WorkflowTaskStartedEventAttributes{ WorkflowTaskStartedEventAttributes: &historypb.WorkflowTaskStartedEventAttributes{ - ScheduledEventId: scheduledEventID, - Identity: identity, - RequestId: requestID, - SuggestContinueAsNew: suggestContinueAsNew, - HistorySizeBytes: historySizeBytes, - WorkerVersion: versioningStamp, - BuildIdRedirectCounter: buildIdRedirectCounter, + ScheduledEventId: scheduledEventID, + Identity: identity, + RequestId: requestID, + SuggestContinueAsNew: suggestContinueAsNew, + SuggestContinueAsNewReasons: suggestContinueAsNewReasons, + HistorySizeBytes: historySizeBytes, + WorkerVersion: versioningStamp, + BuildIdRedirectCounter: buildIdRedirectCounter, }, } return event diff --git a/service/history/historybuilder/history_builder.go b/service/history/historybuilder/history_builder.go index 5df04bc736..e5b33fa7bb 100644 --- a/service/history/historybuilder/history_builder.go +++ b/service/history/historybuilder/history_builder.go @@ -197,6 +197,7 @@ func (b *HistoryBuilder) AddWorkflowTaskStartedEvent( historySizeBytes int64, versioningStamp *commonpb.WorkerVersionStamp, buildIdRedirectCounter int64, + suggestContinueAsNewReasons []enumspb.SuggestContinueAsNewReason, ) *historypb.HistoryEvent { event := b.EventFactory.CreateWorkflowTaskStartedEvent( scheduledEventID, @@ -207,6 +208,7 @@ func (b *HistoryBuilder) AddWorkflowTaskStartedEvent( historySizeBytes, versioningStamp, buildIdRedirectCounter, + suggestContinueAsNewReasons, ) event, _ = b.EventStore.add(event) return event diff --git a/service/history/historybuilder/history_builder_categorization_test.go b/service/history/historybuilder/history_builder_categorization_test.go index 45275714f3..e3f98e8f03 100644 --- a/service/history/historybuilder/history_builder_categorization_test.go +++ b/service/history/historybuilder/history_builder_categorization_test.go @@ -1238,7 +1238,7 @@ func (s *sutTestingAdapter) AddWorkflowExecutionStartedEvent(_ ...eventConfig) * } func (s *sutTestingAdapter) AddWorkflowTaskStartedEvent(_ ...eventConfig) *historypb.HistoryEvent { - return s.HistoryBuilder.AddWorkflowTaskStartedEvent(64, "request-1", "identity-1", s.today, false, 100, nil, 0) + return s.HistoryBuilder.AddWorkflowTaskStartedEvent(64, "request-1", "identity-1", s.today, false, 100, nil, 0, nil) } func (s *sutTestingAdapter) AddWorkflowTaskCompletedEvent(_ ...eventConfig) *historypb.HistoryEvent { diff --git a/service/history/historybuilder/history_builder_test.go b/service/history/historybuilder/history_builder_test.go index a946feca27..9edbdc9516 100644 --- a/service/history/historybuilder/history_builder_test.go +++ b/service/history/historybuilder/history_builder_test.go @@ -657,6 +657,7 @@ func (s *historyBuilderSuite) TestWorkflowTaskStarted() { 123678, nil, int64(0), + nil, ) s.Equal(event, s.flush()) s.Equal(&historypb.HistoryEvent{ diff --git a/service/history/transfer_queue_active_task_executor.go b/service/history/transfer_queue_active_task_executor.go index b25a39a351..386fb34ec0 100644 --- a/service/history/transfer_queue_active_task_executor.go +++ b/service/history/transfer_queue_active_task_executor.go @@ -914,7 +914,7 @@ func (t *transferQueueActiveTaskExecutor) processStartChildExecution( // the first matching task-queue-in-version check. newTQInPinnedVersion := false - // Child of a parent with Pinned behavior will inherit the parent's version if the Child's Task Queue belongs to that version. + // Child of pinned parent will inherit the parent's version if the Child's Task Queue belongs to that version. var inheritedPinnedVersion *deploymentpb.WorkerDeploymentVersion if mutableState.GetEffectiveVersioningBehavior() == enumspb.VERSIONING_BEHAVIOR_PINNED { inheritedPinnedVersion = worker_versioning.ExternalWorkerDeploymentVersionFromDeployment(mutableState.GetEffectiveDeployment()) @@ -933,7 +933,6 @@ func (t *transferQueueActiveTaskExecutor) processStartChildExecution( } // Pinned override is inherited if Task Queue of new run is compatible with the override version. - // Effective versioning behavior of the workflow must be Pinned to inherit the pinned override version. var inheritedPinnedOverride *workflowpb.VersioningOverride if o := mutableState.GetExecutionInfo().GetVersioningInfo().GetVersioningOverride(); worker_versioning.OverrideIsPinned(o) && workflow.GetEffectiveVersioningBehavior(mutableState.GetExecutionInfo().GetVersioningInfo()) == enumspb.VERSIONING_BEHAVIOR_PINNED { diff --git a/service/history/workflow/mutable_state_impl.go b/service/history/workflow/mutable_state_impl.go index 0537a10845..1f568eaa46 100644 --- a/service/history/workflow/mutable_state_impl.go +++ b/service/history/workflow/mutable_state_impl.go @@ -2477,7 +2477,11 @@ func (ms *MutableStateImpl) addWorkflowExecutionStartedEventForContinueAsNew( } // New run initiated by ContinueAsNew of an AUTO_UPGRADE workflow execution will inherit the previous run's - // deployment version and revision number iff the new run's Task Queue belongs to the source deployment version. + // deployment version and revision number iff the new run's Task Queue belongs to source deployment version. + // + // If the initiating workflow is PINNED and the continue-as-new command says to use InitialVersioningBehavior + // AutoUpgrade, the new run will start as AutoUpgrade in the first task and then assume the SDK-sent behavior + // after first workflow task completion. var sourceDeploymentVersion *deploymentpb.WorkerDeploymentVersion var sourceDeploymentRevisionNumber int64 if previousExecutionState.GetEffectiveVersioningBehavior() == enumspb.VERSIONING_BEHAVIOR_AUTO_UPGRADE || diff --git a/service/history/workflow/workflow_task_state_machine.go b/service/history/workflow/workflow_task_state_machine.go index 27a709b995..5101c5b9da 100644 --- a/service/history/workflow/workflow_task_state_machine.go +++ b/service/history/workflow/workflow_task_state_machine.go @@ -519,7 +519,7 @@ func (m *workflowTaskStateMachine) AddWorkflowTaskStartedEvent( // Create WorkflowTaskStartedEvent only if WorkflowTaskScheduledEvent was created. // (it wasn't created for transient/speculative WT). var startedEvent *historypb.HistoryEvent - if workflowTaskScheduledEventCreated { // TODO(carlydf): put reasons into history event + if workflowTaskScheduledEventCreated { startedEvent = m.ms.hBuilder.AddWorkflowTaskStartedEvent( scheduledEventID, requestID, @@ -529,6 +529,7 @@ func (m *workflowTaskStateMachine) AddWorkflowTaskStartedEvent( historySizeBytes, versioningStamp, redirectCounter, + suggestContinueAsNewReasons, ) m.ms.hBuilder.FlushAndCreateNewBatch() startedEventID = startedEvent.GetEventId() @@ -720,6 +721,7 @@ func (m *workflowTaskStateMachine) AddWorkflowTaskCompletedEvent( workflowTask.HistorySizeBytes, request.WorkerVersionStamp, workflowTask.BuildIdRedirectCounter, + nil, ) m.ms.hBuilder.FlushAndCreateNewBatch() workflowTask.StartedEventID = startedEvent.GetEventId() @@ -808,6 +810,7 @@ func (m *workflowTaskStateMachine) AddWorkflowTaskFailedEvent( workflowTask.HistorySizeBytes, versioningStamp, workflowTask.BuildIdRedirectCounter, + nil, ) m.ms.hBuilder.FlushAndCreateNewBatch() workflowTask.StartedEventID = startedEvent.GetEventId() @@ -879,6 +882,7 @@ func (m *workflowTaskStateMachine) AddWorkflowTaskTimedOutEvent( workflowTask.HistorySizeBytes, nil, workflowTask.BuildIdRedirectCounter, + nil, ) m.ms.hBuilder.FlushAndCreateNewBatch() workflowTask.StartedEventID = startedEvent.GetEventId() @@ -1408,6 +1412,7 @@ func (m *workflowTaskStateMachine) convertSpeculativeWorkflowTaskToNormal() erro wt.HistorySizeBytes, nil, wt.BuildIdRedirectCounter, + nil, ) m.ms.hBuilder.FlushAndCreateNewBatch() diff --git a/service/matching/matching_engine.go b/service/matching/matching_engine.go index acb86b6bd4..e030fcf4c6 100644 --- a/service/matching/matching_engine.go +++ b/service/matching/matching_engine.go @@ -3056,7 +3056,6 @@ func (e *matchingEngineImpl) recordActivityTaskStarted( ScheduledDeployment: worker_versioning.DirectiveDeployment(task.event.Data.VersionDirective), VersionDirective: task.event.Data.VersionDirective, TaskDispatchRevisionNumber: task.taskDispatchRevisionNumber, - // TODO(carlydf): Do I send target version here? probably not but just double check } return e.historyClient.RecordActivityTaskStarted(ctx, recordStartedRequest) diff --git a/service/matching/task_queue_partition_manager.go b/service/matching/task_queue_partition_manager.go index 48e08fcfee..cbe3b5e7c2 100644 --- a/service/matching/task_queue_partition_manager.go +++ b/service/matching/task_queue_partition_manager.go @@ -1039,7 +1039,7 @@ func (pm *taskQueuePartitionManagerImpl) getPhysicalQueuesForAdd( var currentRevisionNumber, rampingRevisionNumber int64 var rampingPercentage float32 var targetDeployment *deploymentpb.Deployment //nolint:staticcheck // SA1019: [cleanup-wv-3.1] - if wfBehavior != enumspb.VERSIONING_BEHAVIOR_PINNED { + if wfBehavior != enumspb.VERSIONING_BEHAVIOR_UNSPECIFIED { current, currentRevisionNumber, _, ramping, _, rampingPercentage, rampingRevisionNumber, _ = worker_versioning.CalculateTaskQueueVersioningInfo(deploymentData) targetDeploymentVersion, targetDeploymentRevisionNumber = worker_versioning.FindTargetDeploymentVersionAndRevisionNumberForWorkflowID(current, currentRevisionNumber, ramping, rampingPercentage, rampingRevisionNumber, workflowId) targetDeployment = worker_versioning.DeploymentFromDeploymentVersion(targetDeploymentVersion) From e52e417145a2647a9c5baba468b3554793943fe9 Mon Sep 17 00:00:00 2001 From: Carly de Frondeville Date: Tue, 9 Dec 2025 16:47:24 -0800 Subject: [PATCH 11/19] revert retry.go to main --- service/history/workflow/retry.go | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/service/history/workflow/retry.go b/service/history/workflow/retry.go index fe2970ba48..aa3fa9527b 100644 --- a/service/history/workflow/retry.go +++ b/service/history/workflow/retry.go @@ -255,11 +255,28 @@ func SetupNewWorkflowForRetryOrCron( // of retry, and the retried run inherited a pinned version when it started (ie. it is a child of a pinned // parent, or a CaN of a pinned run, and is running on a Task Queue in the inherited version). var inheritedPinnedVersion *deploymentpb.WorkerDeploymentVersion - if initiator == enumspb.CONTINUE_AS_NEW_INITIATOR_RETRY && - GetEffectiveVersioningBehavior(previousExecutionInfo.GetVersioningInfo()) == enumspb.VERSIONING_BEHAVIOR_PINNED && - startAttr.GetInheritedPinnedVersion() != nil { - inheritedPinnedVersion = worker_versioning.ExternalWorkerDeploymentVersionFromDeployment(GetEffectiveDeployment(previousExecutionInfo.GetVersioningInfo())) + // If the previous run had an AutoUpgrade behavior, we pass down the source deployment version and revision number to the new run. + // Note: We only pass down one of inheritedPinnedVersion or inheritedAutoUpgradeInfo, but not both! + var inheritedAutoUpgradeInfo *deploymentpb.InheritedAutoUpgradeInfo + + if initiator == enumspb.CONTINUE_AS_NEW_INITIATOR_RETRY { // retries and crons always go to the same task queue, so no need to check if override version is in new task queue + + if GetEffectiveVersioningBehavior(previousExecutionInfo.GetVersioningInfo()) == enumspb.VERSIONING_BEHAVIOR_PINNED && + startAttr.GetInheritedPinnedVersion() != nil { + inheritedPinnedVersion = worker_versioning.ExternalWorkerDeploymentVersionFromDeployment(GetEffectiveDeployment(previousExecutionInfo.GetVersioningInfo())) + } else if GetEffectiveVersioningBehavior(previousExecutionInfo.GetVersioningInfo()) == enumspb.VERSIONING_BEHAVIOR_AUTO_UPGRADE { + sourceDeploymentVersion := worker_versioning.ExternalWorkerDeploymentVersionFromDeployment(previousMutableState.GetEffectiveDeployment()) + sourceDeploymentRevisionNumber := previousMutableState.GetVersioningRevisionNumber() + + // Only set inherited auto upgrade info if source deployment version and revision number are not nil. + if sourceDeploymentVersion != nil && sourceDeploymentRevisionNumber != 0 { + inheritedAutoUpgradeInfo = &deploymentpb.InheritedAutoUpgradeInfo{ + SourceDeploymentVersion: sourceDeploymentVersion, + SourceDeploymentRevisionNumber: sourceDeploymentRevisionNumber, + } + } + } } createRequest := &workflowservice.StartWorkflowExecutionRequest{ @@ -313,6 +330,7 @@ func SetupNewWorkflowForRetryOrCron( RootExecutionInfo: rootInfo, InheritedBuildId: startAttr.InheritedBuildId, InheritedPinnedVersion: inheritedPinnedVersion, + InheritedAutoUpgradeInfo: inheritedAutoUpgradeInfo, } workflowTimeoutTime := timestamp.TimeValue(previousExecutionInfo.WorkflowExecutionExpirationTime) if !workflowTimeoutTime.IsZero() { From ee3bb3578ee3dd9f31bff972493c1ea8f619cacb Mon Sep 17 00:00:00 2001 From: Carly de Frondeville Date: Tue, 9 Dec 2025 16:52:46 -0800 Subject: [PATCH 12/19] fix nil pointer and remove unecessary changes --- service/history/transfer_queue_active_task_executor.go | 3 +-- service/history/workflow/mutable_state_impl.go | 3 +-- service/history/workflow/workflow_task_state_machine.go | 5 +++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/service/history/transfer_queue_active_task_executor.go b/service/history/transfer_queue_active_task_executor.go index 386fb34ec0..03ac48748a 100644 --- a/service/history/transfer_queue_active_task_executor.go +++ b/service/history/transfer_queue_active_task_executor.go @@ -934,8 +934,7 @@ func (t *transferQueueActiveTaskExecutor) processStartChildExecution( // Pinned override is inherited if Task Queue of new run is compatible with the override version. var inheritedPinnedOverride *workflowpb.VersioningOverride - if o := mutableState.GetExecutionInfo().GetVersioningInfo().GetVersioningOverride(); worker_versioning.OverrideIsPinned(o) && - workflow.GetEffectiveVersioningBehavior(mutableState.GetExecutionInfo().GetVersioningInfo()) == enumspb.VERSIONING_BEHAVIOR_PINNED { + if o := mutableState.GetExecutionInfo().GetVersioningInfo().GetVersioningOverride(); worker_versioning.OverrideIsPinned(o) { inheritedPinnedOverride = o newTQ := attributes.GetTaskQueue().GetName() if newTQ != mutableState.GetExecutionInfo().GetTaskQueue() && !newTQInPinnedVersion || diff --git a/service/history/workflow/mutable_state_impl.go b/service/history/workflow/mutable_state_impl.go index 1f568eaa46..e5c4af98a2 100644 --- a/service/history/workflow/mutable_state_impl.go +++ b/service/history/workflow/mutable_state_impl.go @@ -2467,8 +2467,7 @@ func (ms *MutableStateImpl) addWorkflowExecutionStartedEventForContinueAsNew( // Pinned override is inherited if Task Queue of new run is compatible with the override version. var pinnedOverride *workflowpb.VersioningOverride - if o := previousExecutionInfo.GetVersioningInfo().GetVersioningOverride(); worker_versioning.OverrideIsPinned(o) && - GetEffectiveVersioningBehavior(previousExecutionInfo.GetVersioningInfo()) == enumspb.VERSIONING_BEHAVIOR_PINNED { + if o := previousExecutionInfo.GetVersioningInfo().GetVersioningOverride(); worker_versioning.OverrideIsPinned(o) { pinnedOverride = o newTQ := command.GetTaskQueue().GetName() if newTQ != previousExecutionInfo.GetTaskQueue() && !newTQInPinnedVersion { diff --git a/service/history/workflow/workflow_task_state_machine.go b/service/history/workflow/workflow_task_state_machine.go index 5101c5b9da..d80313cc3b 100644 --- a/service/history/workflow/workflow_task_state_machine.go +++ b/service/history/workflow/workflow_task_state_machine.go @@ -482,8 +482,9 @@ func (m *workflowTaskStateMachine) AddWorkflowTaskStartedEvent( } if m.ms.GetEffectiveVersioningBehavior() != enumspb.VERSIONING_BEHAVIOR_UNSPECIFIED && targetDeploymentVersion != nil { - if currentDeploymentVersion := m.ms.GetEffectiveDeployment(); currentDeploymentVersion.BuildId != targetDeploymentVersion.BuildId || - currentDeploymentVersion.SeriesName != targetDeploymentVersion.DeploymentName { + if currentDeploymentVersion := m.ms.GetEffectiveDeployment(); currentDeploymentVersion != nil && + (currentDeploymentVersion.BuildId != targetDeploymentVersion.BuildId || + currentDeploymentVersion.SeriesName != targetDeploymentVersion.DeploymentName) { suggestContinueAsNew = cmp.Or(suggestContinueAsNew, true) suggestContinueAsNewReasons = append(suggestContinueAsNewReasons, enumspb.SUGGEST_CONTINUE_AS_NEW_REASON_TARGET_WORKER_DEPLOYMENT_VERSION_CHANGED) } From 0b25d1cc4f981622d16cfa0ec8239511c2e62fb9 Mon Sep 17 00:00:00 2001 From: Carly de Frondeville Date: Wed, 10 Dec 2025 17:52:42 -0800 Subject: [PATCH 13/19] fix bug that i introduced --- service/matching/task.go | 11 ++++++----- service/matching/task_queue_partition_manager.go | 14 ++++---------- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/service/matching/task.go b/service/matching/task.go index 376f3b978e..40d8faa457 100644 --- a/service/matching/task.go +++ b/service/matching/task.go @@ -128,11 +128,12 @@ func newInternalTaskForSyncMatch( TaskId: syncMatchTaskId, }, }, - forwardInfo: forwardInfo, - source: source, - redirectInfo: redirectInfo, - responseC: make(chan taskResponse, 1), - effectivePriority: priorityKey(info.GetPriority().GetPriorityKey()), + forwardInfo: forwardInfo, + source: source, + redirectInfo: redirectInfo, + responseC: make(chan taskResponse, 1), + effectivePriority: priorityKey(info.GetPriority().GetPriorityKey()), + targetWorkerDeploymentVersion: targetVersion, } } diff --git a/service/matching/task_queue_partition_manager.go b/service/matching/task_queue_partition_manager.go index cbe3b5e7c2..603aaa3728 100644 --- a/service/matching/task_queue_partition_manager.go +++ b/service/matching/task_queue_partition_manager.go @@ -1035,15 +1035,9 @@ func (pm *taskQueuePartitionManagerImpl) getPhysicalQueuesForAdd( deploymentData := perTypeUserData.GetDeploymentData() taskDirectiveRevisionNumber := directive.GetRevisionNumber() - var current, ramping, targetDeploymentVersion *deploymentspb.WorkerDeploymentVersion - var currentRevisionNumber, rampingRevisionNumber int64 - var rampingPercentage float32 - var targetDeployment *deploymentpb.Deployment //nolint:staticcheck // SA1019: [cleanup-wv-3.1] - if wfBehavior != enumspb.VERSIONING_BEHAVIOR_UNSPECIFIED { - current, currentRevisionNumber, _, ramping, _, rampingPercentage, rampingRevisionNumber, _ = worker_versioning.CalculateTaskQueueVersioningInfo(deploymentData) - targetDeploymentVersion, targetDeploymentRevisionNumber = worker_versioning.FindTargetDeploymentVersionAndRevisionNumberForWorkflowID(current, currentRevisionNumber, ramping, rampingPercentage, rampingRevisionNumber, workflowId) - targetDeployment = worker_versioning.DeploymentFromDeploymentVersion(targetDeploymentVersion) - } + current, currentRevisionNumber, _, ramping, _, rampingPercentage, rampingRevisionNumber, _ := worker_versioning.CalculateTaskQueueVersioningInfo(deploymentData) + targetDeploymentVersion, targetDeploymentRevisionNumber := worker_versioning.FindTargetDeploymentVersionAndRevisionNumberForWorkflowID(current, currentRevisionNumber, ramping, rampingPercentage, rampingRevisionNumber, workflowId) + targetDeployment := worker_versioning.DeploymentFromDeploymentVersion(targetDeploymentVersion) if wfBehavior == enumspb.VERSIONING_BEHAVIOR_PINNED { if pm.partition.Kind() == enumspb.TASK_QUEUE_KIND_STICKY { @@ -1090,7 +1084,7 @@ func (pm *taskQueuePartitionManagerImpl) getPhysicalQueuesForAdd( } } } - + var targetDeploymentQueue physicalTaskQueueManager if directive.GetAssignedBuildId() == "" && targetDeployment != nil { if pm.partition.Kind() == enumspb.TASK_QUEUE_KIND_STICKY { From 5294aaa0b658f77588a484756d2a09e960b72291 Mon Sep 17 00:00:00 2001 From: Carly de Frondeville Date: Thu, 11 Dec 2025 19:12:05 -0800 Subject: [PATCH 14/19] add functional test for pinned workflow receiving suggestion and continuing-as-new --- tests/versioning_3_test.go | 154 +++++++++++++++++++++++++++++++++++-- 1 file changed, 149 insertions(+), 5 deletions(-) diff --git a/tests/versioning_3_test.go b/tests/versioning_3_test.go index 46eb3e5984..8dec24a34f 100644 --- a/tests/versioning_3_test.go +++ b/tests/versioning_3_test.go @@ -107,6 +107,12 @@ func TestVersioning3FunctionalSuite(t *testing.T) { useNewDeploymentData: true, }) }) + //suite.Run(t, &Versioning3Suite{ + // deploymentWorkflowVersion: workerdeployment.AsyncSetCurrentAndRamping, + // useV32: true, + // useNewDeploymentData: true, + // useRevisionNumbers: true, + //}) } @@ -2130,22 +2136,156 @@ func (s *Versioning3Suite) testChildWorkflowInheritance_ExpectNoInherit(crossTq } func (s *Versioning3Suite) TestPinnedCaN_SameTQ() { - s.testCan(false, vbPinned, true) + s.testCan(false, vbPinned, false, true) } func (s *Versioning3Suite) TestPinnedCaN_CrossTQ_Inherit() { - s.testCan(true, vbPinned, true) + s.testCan(true, vbPinned, false, true) } func (s *Versioning3Suite) TestPinnedCaN_CrossTQ_NoInherit() { - s.testCan(true, vbPinned, false) + s.testCan(true, vbPinned, false, false) +} + +func (s *Versioning3Suite) TestPinnedCaN_upgradeOnCaN_SameTQ() { + s.T().Skip("run after SDK exposes CaN option") + s.testCan(false, vbPinned, true, false) +} + +func (s *Versioning3Suite) TestPinnedCaN_upgradeOnCaN_CrossTQ_Inherit() { + s.T().Skip("run after SDK exposes CaN option") + s.testCan(true, vbPinned, true, false) +} + +func (s *Versioning3Suite) TestPinnedCaN_upgradeOnCaN_CrossTQ_NoInherit() { + s.T().Skip("run after SDK exposes CaN option") + s.testCan(true, vbPinned, true, false) } func (s *Versioning3Suite) TestUnpinnedCaN() { - s.testCan(false, vbUnpinned, false) + s.testCan(false, vbUnpinned, false, false) +} + +func (s *Versioning3Suite) TestUnpinnedCaN_upgradeOnCaN() { + s.T().Skip("run after SDK exposes CaN option") + s.testCan(false, vbUnpinned, true, false) +} + +// TestPinnedCaN_upgradeOnCaN_WithTaskPoller tests ContinueAsNew with InitialVersioningBehavior +// set to AUTO_UPGRADE using task polling directly (without SDK). This allows testing the feature +// before it's exposed in the SDK. +// +// Flow: +// 1. Set v1 as current, start workflow +// 2. First WFT: task is sent to v1 worker, worker declares vbPinned -> workflow becomes pinned to v1 +// 3. Set v2 as current +// 4. Signal workflow, then on WFT: +// - Confirm that ContinueAsNewSuggested=true and ContinueAsNewSuggestedReasons=[NewTargetVersion] +// - Issue ContinueAsNew with InitialVersioningBehavior AUTO_UPGRADE +// +// 5. The new run should start on v2 (current) because of AUTO_UPGRADE initial behavior +func (s *Versioning3Suite) TestPinnedCaN_upgradeOnCaN_WithTaskPoller() { + s.RunTestWithMatchingBehavior(func() { + s.testCanWithTaskPoller() + }) +} + +func (s *Versioning3Suite) testCanWithTaskPoller() { + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + + tv1 := testvars.New(s).WithBuildIDNumber(1) + tv2 := tv1.WithBuildIDNumber(2) + execution, _ := s.drainWorkflowTaskAfterSetCurrent(tv1) + + // The workflow is currently unpinned (drainWorkflowTaskAfterSetCurrent uses vbUnpinned) + // We need another WFT where the worker declares vbPinned to pin it + // Signal to trigger a new WFT + _, err := s.FrontendClient().SignalWorkflowExecution(ctx, &workflowservice.SignalWorkflowExecutionRequest{ + Namespace: s.Namespace().String(), + WorkflowExecution: execution, + SignalName: tv1.SignalName(), + Input: tv1.Any().Payloads(), + Identity: tv1.WorkerIdentity(), + }) + s.NoError(err) + + // Process the signal WFT and declare vbPinned to pin the workflow to v1 + s.pollWftAndHandle(tv1, false, nil, + func(task *workflowservice.PollWorkflowTaskQueueResponse) (*workflowservice.RespondWorkflowTaskCompletedRequest, error) { + s.NotNil(task) + // Worker declares vbPinned - this pins the workflow to v1 + return respondEmptyWft(tv1, false, vbPinned), nil + }) + + // Verify workflow is now pinned to v1 + s.verifyWorkflowVersioning(tv1, vbPinned, tv1.Deployment(), nil, nil) + + // Register v2 poller before setting it as current + s.idlePollWorkflow(ctx, tv2, true, ver3MinPollTime, "should not get any tasks yet") + + // Set v2 as current + s.setCurrentDeployment(tv2) + + // Start async poller for v2 to receive the ContinueAsNew new run + wftNewRunDone := make(chan struct{}) + s.pollWftAndHandle(tv2, false, wftNewRunDone, + func(task *workflowservice.PollWorkflowTaskQueueResponse) (*workflowservice.RespondWorkflowTaskCompletedRequest, error) { + s.NotNil(task) + // The new run should be on v2 because InitialVersioningBehavior was AUTO_UPGRADE + return respondCompleteWorkflow(tv2, vbPinned), nil + }) + + // Signal the workflow again to trigger the WFT with ContinueAsNewSuggested=true and reasons=[NewTargetVersion] + _, err = s.FrontendClient().SignalWorkflowExecution(ctx, &workflowservice.SignalWorkflowExecutionRequest{ + Namespace: s.Namespace().String(), + WorkflowExecution: execution, + SignalName: tv1.SignalName(), + Input: tv1.Any().Payloads(), + Identity: tv1.WorkerIdentity(), + }) + s.NoError(err) + + // Process the WFT on v1 and issue ContinueAsNew with AUTO_UPGRADE initial behavior + s.pollWftAndHandle(tv1, false, nil, + func(task *workflowservice.PollWorkflowTaskQueueResponse) (*workflowservice.RespondWorkflowTaskCompletedRequest, error) { + s.NotNil(task) + + lastEvent := task.GetHistory().GetEvents()[len(task.GetHistory().GetEvents())-1] + s.Equal(enumspb.EVENT_TYPE_WORKFLOW_TASK_STARTED, lastEvent.GetEventType()) + attr := lastEvent.GetWorkflowTaskStartedEventAttributes() + s.True(attr.GetSuggestContinueAsNew()) + s.Equal(enumspb.SUGGEST_CONTINUE_AS_NEW_REASON_TARGET_WORKER_DEPLOYMENT_VERSION_CHANGED, attr.GetSuggestContinueAsNewReasons()[0]) + + return &workflowservice.RespondWorkflowTaskCompletedRequest{ + Commands: []*commandpb.Command{ + { + CommandType: enumspb.COMMAND_TYPE_CONTINUE_AS_NEW_WORKFLOW_EXECUTION, + Attributes: &commandpb.Command_ContinueAsNewWorkflowExecutionCommandAttributes{ + ContinueAsNewWorkflowExecutionCommandAttributes: &commandpb.ContinueAsNewWorkflowExecutionCommandAttributes{ + WorkflowType: tv1.WorkflowType(), + TaskQueue: tv1.TaskQueue(), + Input: tv1.Any().Payloads(), + InitialVersioningBehavior: enumspb.CONTINUE_AS_NEW_VERSIONING_BEHAVIOR_AUTO_UPGRADE, + }, + }, + }, + }, + ForceCreateNewWorkflowTask: false, + VersioningBehavior: vbPinned, + DeploymentOptions: tv1.WorkerDeploymentOptions(true), + }, nil + }) + + // Wait for the new run's first workflow task on v2 + s.WaitForChannel(ctx, wftNewRunDone) + + // Verify the new workflow run is on v2 (not v1) because of AUTO_UPGRADE initial behavior + // The new workflow is now Pinned though, because the worker annotation was Pinned + s.verifyWorkflowVersioning(tv2, vbPinned, tv2.Deployment(), nil, nil) } -func (s *Versioning3Suite) testCan(crossTq bool, behavior enumspb.VersioningBehavior, expectInherit bool) { +func (s *Versioning3Suite) testCan(crossTq bool, behavior enumspb.VersioningBehavior, upgradeOnCaN bool, expectInherit bool) { // CaN inherits version if pinned and if new task queue is in pinned version, goes to current version if unpinned. tv1 := testvars.New(s).WithBuildIDNumber(1).WithWorkflowIDNumber(1) tv2 := tv1.WithBuildIDNumber(2) @@ -2168,6 +2308,10 @@ func (s *Versioning3Suite) testCan(crossTq bool, behavior enumspb.VersioningBeha if crossTq { newCtx = workflow.WithWorkflowTaskQueue(newCtx, canxTq) } + // TODO(carlydf): use this code path once CaN option to choose InitialVersioningBehavior is exposed in SDK + //if upgradeOnCaN { + // newCtx = workflow.WithInitialVersioningBehavior(newCtx, temporal.ContinueAsNewVersioningBehaviorAutoUpgrade) + //} s.verifyWorkflowVersioning(tv1, vbUnspecified, nil, nil, tv1.DeploymentVersionTransition()) wfStarted <- struct{}{} // wait for current version to change. From 22d3e4d2c8bb751d3529c5f4cffa281199a9dd98 Mon Sep 17 00:00:00 2001 From: Carly de Frondeville Date: Thu, 11 Dec 2025 19:16:10 -0800 Subject: [PATCH 15/19] fix matching unit test build --- service/matching/forwarder_test.go | 2 +- service/matching/matcher_data_test.go | 4 ++-- service/matching/matcher_test.go | 16 ++++++++-------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/service/matching/forwarder_test.go b/service/matching/forwarder_test.go index 41fadf5ced..99d28e868a 100644 --- a/service/matching/forwarder_test.go +++ b/service/matching/forwarder_test.go @@ -123,7 +123,7 @@ func (t *ForwarderTestSuite) TestForwardWorkflowTask_WithBuildId() { ).Return(&matchingservice.AddWorkflowTaskResponse{}, nil) taskInfo := randomTaskInfo() - task := newInternalTaskForSyncMatch(taskInfo.Data, nil, 0) + task := newInternalTaskForSyncMatch(taskInfo.Data, nil, 0, nil) t.NoError(t.fwdr.ForwardTask(context.Background(), task)) t.NotNil(request) t.Equal(mustParent(t.partition, 20).RpcName(), request.TaskQueue.GetName()) diff --git a/service/matching/matcher_data_test.go b/service/matching/matcher_data_test.go index 036f1f34c4..a101db1de6 100644 --- a/service/matching/matcher_data_test.go +++ b/service/matching/matcher_data_test.go @@ -91,7 +91,7 @@ func (s *MatcherDataSuite) newSyncTask(fwdInfo *taskqueuespb.TaskForwardInfo) *i t := &persistencespb.TaskInfo{ CreateTime: timestamppb.New(s.now()), } - return newInternalTaskForSyncMatch(t, fwdInfo, 0) + return newInternalTaskForSyncMatch(t, fwdInfo, 0, nil) } func (s *MatcherDataSuite) newQueryTask(id string) *internalTask { @@ -747,7 +747,7 @@ func FuzzMatcherData(f *testing.F) { t := &persistencespb.TaskInfo{ CreateTime: timestamppb.New(ts.Now()), } - md.FinishMatchAfterPollForward(res.poller, newInternalTaskForSyncMatch(t, nil, 0)) + md.FinishMatchAfterPollForward(res.poller, newInternalTaskForSyncMatch(t, nil, 0, nil)) }() case 6: // add task forwarder diff --git a/service/matching/matcher_test.go b/service/matching/matcher_test.go index e4b50ae20a..b09c2d3e73 100644 --- a/service/matching/matcher_test.go +++ b/service/matching/matcher_test.go @@ -116,7 +116,7 @@ func (t *MatcherTestSuite) TestLocalSyncMatch() { <-pollStarted time.Sleep(10 * time.Millisecond) - task := newInternalTaskForSyncMatch(randomTaskInfo().Data, nil, 0) + task := newInternalTaskForSyncMatch(randomTaskInfo().Data, nil, 0, nil) ctx, cancel := context.WithTimeout(context.Background(), time.Second) syncMatch, err := t.childMatcher.Offer(ctx, task) cancel() @@ -166,12 +166,12 @@ func (t *MatcherTestSuite) testRemoteSyncMatch(taskSource enumsspb.TaskSource) { }, ).Return(&remotePollResp, remotePollErr).AnyTimes() - task := newInternalTaskForSyncMatch(randomTaskInfo().Data, nil, 0) + task := newInternalTaskForSyncMatch(randomTaskInfo().Data, nil, 0, nil) if taskSource == enumsspb.TASK_SOURCE_DB_BACKLOG { task = newInternalTaskForSyncMatch(randomTaskInfo().Data, &taskqueuespb.TaskForwardInfo{ TaskSource: enumsspb.TASK_SOURCE_DB_BACKLOG, SourcePartition: "p123", - }, 0) + }, 0, nil) } ctx, cancel := context.WithTimeout(context.Background(), time.Second) @@ -221,7 +221,7 @@ func (t *MatcherTestSuite) TestRejectSyncMatchWhenBacklog() { }, 30*time.Second, 1*time.Millisecond) // should not allow sync match when there is an old task in backlog - syncMatchTask := newInternalTaskForSyncMatch(randomTaskInfoWithAge(time.Minute).Data, nil, 0) + syncMatchTask := newInternalTaskForSyncMatch(randomTaskInfoWithAge(time.Minute).Data, nil, 0, nil) // Adding forwardInfo to replicate a task being forwarded from the child partition. // This field is required to be non-nil for the matcher to offer this task locally to a poller, which is desired. syncMatchTask.forwardInfo = &taskqueuespb.TaskForwardInfo{ @@ -254,7 +254,7 @@ func (t *MatcherTestSuite) TestRejectSyncMatchWhenBacklog() { } func (t *MatcherTestSuite) TestForwardingWhenBacklogIsYoung() { - historyTask := newInternalTaskForSyncMatch(randomTaskInfo().Data, nil, 0) + historyTask := newInternalTaskForSyncMatch(randomTaskInfo().Data, nil, 0, nil) ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) intruptC := make(chan struct{}) @@ -451,7 +451,7 @@ func (t *MatcherTestSuite) TestBacklogAge() { } func (t *MatcherTestSuite) TestSyncMatchFailure() { - task := newInternalTaskForSyncMatch(randomTaskInfo().Data, nil, 0) + task := newInternalTaskForSyncMatch(randomTaskInfo().Data, nil, 0, nil) ctx, cancel := context.WithTimeout(context.Background(), time.Second) var req *matchingservice.AddWorkflowTaskRequest @@ -701,7 +701,7 @@ func (t *MatcherTestSuite) TestMustOfferLocalMatch() { <-pollStarted time.Sleep(10 * time.Millisecond) - task := newInternalTaskForSyncMatch(randomTaskInfo().Data, nil, 0) + task := newInternalTaskForSyncMatch(randomTaskInfo().Data, nil, 0, nil) ctx, cancel := context.WithTimeout(context.Background(), time.Second) err := t.childMatcher.MustOffer(ctx, task, nil) cancel() @@ -754,7 +754,7 @@ func (t *MatcherTestSuite) TestMustOfferRemoteMatch() { t.client.EXPECT().AddWorkflowTask(gomock.Any(), gomock.Any(), gomock.Any()).Do( func(arg0 context.Context, arg1 *matchingservice.AddWorkflowTaskRequest, arg2 ...interface{}) { req = arg1 - task := newInternalTaskForSyncMatch(task.event.AllocatedTaskInfo.Data, req.ForwardInfo, 0) + task := newInternalTaskForSyncMatch(task.event.AllocatedTaskInfo.Data, req.ForwardInfo, 0, nil) close(pollSigC) remoteSyncMatch, err = t.rootMatcher.Offer(ctx, task) }, From 8d43f411c3d37143468d40157ea9bc427803d131 Mon Sep 17 00:00:00 2001 From: Carly de Frondeville Date: Thu, 11 Dec 2025 19:31:15 -0800 Subject: [PATCH 16/19] confirm that suggest-continue-as-new is not sent prematurely --- tests/versioning_3_test.go | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/tests/versioning_3_test.go b/tests/versioning_3_test.go index 8dec24a34f..0c76f01886 100644 --- a/tests/versioning_3_test.go +++ b/tests/versioning_3_test.go @@ -18,6 +18,7 @@ import ( commonpb "go.temporal.io/api/common/v1" deploymentpb "go.temporal.io/api/deployment/v1" enumspb "go.temporal.io/api/enums/v1" + historypb "go.temporal.io/api/history/v1" nexuspb "go.temporal.io/api/nexus/v1" "go.temporal.io/api/serviceerror" taskqueuepb "go.temporal.io/api/taskqueue/v1" @@ -2251,11 +2252,25 @@ func (s *Versioning3Suite) testCanWithTaskPoller() { func(task *workflowservice.PollWorkflowTaskQueueResponse) (*workflowservice.RespondWorkflowTaskCompletedRequest, error) { s.NotNil(task) - lastEvent := task.GetHistory().GetEvents()[len(task.GetHistory().GetEvents())-1] - s.Equal(enumspb.EVENT_TYPE_WORKFLOW_TASK_STARTED, lastEvent.GetEventType()) - attr := lastEvent.GetWorkflowTaskStartedEventAttributes() - s.True(attr.GetSuggestContinueAsNew()) - s.Equal(enumspb.SUGGEST_CONTINUE_AS_NEW_REASON_TARGET_WORKER_DEPLOYMENT_VERSION_CHANGED, attr.GetSuggestContinueAsNewReasons()[0]) + wfTaskStartedEvents := make([]*historypb.HistoryEvent, 0) + for _, event := range task.History.Events { + if event.GetEventType() == enumspb.EVENT_TYPE_WORKFLOW_TASK_STARTED { + wfTaskStartedEvents = append(wfTaskStartedEvents, event) + } + } + s.Require().Len(wfTaskStartedEvents, 3) // make sure we are actually verifying events + + for i, event := range wfTaskStartedEvents { + attr := event.GetWorkflowTaskStartedEventAttributes() + // the last started event should have ContinueAsNewSuggested=true and reasons=[NewTargetVersion] + if i == len(wfTaskStartedEvents)-1 { + s.True(attr.GetSuggestContinueAsNew()) + s.Equal(enumspb.SUGGEST_CONTINUE_AS_NEW_REASON_TARGET_WORKER_DEPLOYMENT_VERSION_CHANGED, attr.GetSuggestContinueAsNewReasons()[0]) + } else { // the other started events should not + s.False(attr.GetSuggestContinueAsNew()) + s.Require().Len(attr.GetSuggestContinueAsNewReasons(), 0) + } + } return &workflowservice.RespondWorkflowTaskCompletedRequest{ Commands: []*commandpb.Command{ From 40f8567442930807ddc91c3283dd59691efb03de Mon Sep 17 00:00:00 2001 From: Carly de Frondeville Date: Thu, 11 Dec 2025 19:47:31 -0800 Subject: [PATCH 17/19] test auto-upgrade behavior too --- tests/versioning_3_test.go | 72 +++++++++++++++++++++++++------------- 1 file changed, 47 insertions(+), 25 deletions(-) diff --git a/tests/versioning_3_test.go b/tests/versioning_3_test.go index 0c76f01886..f45a096625 100644 --- a/tests/versioning_3_test.go +++ b/tests/versioning_3_test.go @@ -2172,7 +2172,7 @@ func (s *Versioning3Suite) TestUnpinnedCaN_upgradeOnCaN() { s.testCan(false, vbUnpinned, true, false) } -// TestPinnedCaN_upgradeOnCaN_WithTaskPoller tests ContinueAsNew with InitialVersioningBehavior +// TestPinnedCaN_UpgradeOnCaN tests ContinueAsNew of a Pinned workflow with InitialVersioningBehavior // set to AUTO_UPGRADE using task polling directly (without SDK). This allows testing the feature // before it's exposed in the SDK. // @@ -2184,14 +2184,33 @@ func (s *Versioning3Suite) TestUnpinnedCaN_upgradeOnCaN() { // - Confirm that ContinueAsNewSuggested=true and ContinueAsNewSuggestedReasons=[NewTargetVersion] // - Issue ContinueAsNew with InitialVersioningBehavior AUTO_UPGRADE // -// 5. The new run should start on v2 (current) because of AUTO_UPGRADE initial behavior -func (s *Versioning3Suite) TestPinnedCaN_upgradeOnCaN_WithTaskPoller() { +// 5. The new run should start on v2 (current) because of AUTO_UPGRADE initial behavior, but after WFT completion, be pinned. +func (s *Versioning3Suite) TestPinnedCaN_UpgradeOnCaN() { s.RunTestWithMatchingBehavior(func() { - s.testCanWithTaskPoller() + s.testCanWithTaskPoller(vbPinned) }) } -func (s *Versioning3Suite) testCanWithTaskPoller() { +// TestAutoUpgradeCaN_UpgradeOnCaN tests ContinueAsNew of an AutoUpgrade workflow with InitialVersioningBehavior +// set to AUTO_UPGRADE using task polling directly (without SDK). This allows testing the feature +// before it's exposed in the SDK. +// +// Flow: +// 1. Set v1 as current, start workflow +// 2. First WFT: task is sent to v1 worker, worker declares vbUnpinned -> workflow becomes AutoUpgrade on v1 +// 3. Set v2 as current +// 4. Signal workflow, then on WFT: +// - Confirm that ContinueAsNewSuggested=true and ContinueAsNewSuggestedReasons=[NewTargetVersion] +// - Issue ContinueAsNew with InitialVersioningBehavior AUTO_UPGRADE +// +// 5. The new run should start on v2 (current) because of AUTO_UPGRADE initial behavior, and after WFT completion, be AutoUpgrade. +func (s *Versioning3Suite) TestAutoUpgradeCaN_UpgradeOnCaN() { + s.RunTestWithMatchingBehavior(func() { + s.testCanWithTaskPoller(vbUnpinned) + }) +} + +func (s *Versioning3Suite) testCanWithTaskPoller(behavior enumspb.VersioningBehavior) { ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) defer cancel() @@ -2200,7 +2219,7 @@ func (s *Versioning3Suite) testCanWithTaskPoller() { execution, _ := s.drainWorkflowTaskAfterSetCurrent(tv1) // The workflow is currently unpinned (drainWorkflowTaskAfterSetCurrent uses vbUnpinned) - // We need another WFT where the worker declares vbPinned to pin it + // We need another WFT where the worker declares behavior to make it pinned (if behavior is pinned) // Signal to trigger a new WFT _, err := s.FrontendClient().SignalWorkflowExecution(ctx, &workflowservice.SignalWorkflowExecutionRequest{ Namespace: s.Namespace().String(), @@ -2211,16 +2230,15 @@ func (s *Versioning3Suite) testCanWithTaskPoller() { }) s.NoError(err) - // Process the signal WFT and declare vbPinned to pin the workflow to v1 + // Process the signal WFT and declare behavior to make the workflow versioned (either pinned or AU) on v1 s.pollWftAndHandle(tv1, false, nil, func(task *workflowservice.PollWorkflowTaskQueueResponse) (*workflowservice.RespondWorkflowTaskCompletedRequest, error) { s.NotNil(task) - // Worker declares vbPinned - this pins the workflow to v1 - return respondEmptyWft(tv1, false, vbPinned), nil + return respondEmptyWft(tv1, false, behavior), nil }) - // Verify workflow is now pinned to v1 - s.verifyWorkflowVersioning(tv1, vbPinned, tv1.Deployment(), nil, nil) + // Verify workflow is now versioned (either pinned or AU) and running on v1 + s.verifyWorkflowVersioning(tv1, behavior, tv1.Deployment(), nil, nil) // Register v2 poller before setting it as current s.idlePollWorkflow(ctx, tv2, true, ver3MinPollTime, "should not get any tasks yet") @@ -2228,15 +2246,6 @@ func (s *Versioning3Suite) testCanWithTaskPoller() { // Set v2 as current s.setCurrentDeployment(tv2) - // Start async poller for v2 to receive the ContinueAsNew new run - wftNewRunDone := make(chan struct{}) - s.pollWftAndHandle(tv2, false, wftNewRunDone, - func(task *workflowservice.PollWorkflowTaskQueueResponse) (*workflowservice.RespondWorkflowTaskCompletedRequest, error) { - s.NotNil(task) - // The new run should be on v2 because InitialVersioningBehavior was AUTO_UPGRADE - return respondCompleteWorkflow(tv2, vbPinned), nil - }) - // Signal the workflow again to trigger the WFT with ContinueAsNewSuggested=true and reasons=[NewTargetVersion] _, err = s.FrontendClient().SignalWorkflowExecution(ctx, &workflowservice.SignalWorkflowExecutionRequest{ Namespace: s.Namespace().String(), @@ -2247,8 +2256,12 @@ func (s *Versioning3Suite) testCanWithTaskPoller() { }) s.NoError(err) - // Process the WFT on v1 and issue ContinueAsNew with AUTO_UPGRADE initial behavior - s.pollWftAndHandle(tv1, false, nil, + // Process the signal WFT and issue ContinueAsNew with AUTO_UPGRADE initial behavior + pollingVersion := tv1 + if behavior == vbUnpinned { + pollingVersion = tv2 + } + s.pollWftAndHandle(pollingVersion, false, nil, func(task *workflowservice.PollWorkflowTaskQueueResponse) (*workflowservice.RespondWorkflowTaskCompletedRequest, error) { s.NotNil(task) @@ -2258,7 +2271,7 @@ func (s *Versioning3Suite) testCanWithTaskPoller() { wfTaskStartedEvents = append(wfTaskStartedEvents, event) } } - s.Require().Len(wfTaskStartedEvents, 3) // make sure we are actually verifying events + s.Require().Len(wfTaskStartedEvents, 3) // make sure we are actually verifying non-zero # of events for i, event := range wfTaskStartedEvents { attr := event.GetWorkflowTaskStartedEventAttributes() @@ -2292,12 +2305,21 @@ func (s *Versioning3Suite) testCanWithTaskPoller() { }, nil }) + // Start async poller for v2 to receive the ContinueAsNew new run + wftNewRunDone := make(chan struct{}) + s.pollWftAndHandle(tv2, false, wftNewRunDone, + func(task *workflowservice.PollWorkflowTaskQueueResponse) (*workflowservice.RespondWorkflowTaskCompletedRequest, error) { + s.NotNil(task) + // The new run should be on v2 because InitialVersioningBehavior was AUTO_UPGRADE + return respondCompleteWorkflow(tv2, behavior), nil + }) + // Wait for the new run's first workflow task on v2 s.WaitForChannel(ctx, wftNewRunDone) // Verify the new workflow run is on v2 (not v1) because of AUTO_UPGRADE initial behavior - // The new workflow is now Pinned though, because the worker annotation was Pinned - s.verifyWorkflowVersioning(tv2, vbPinned, tv2.Deployment(), nil, nil) + // The new workflow has the worker-sent behavior from the first WFT completion after CaN. + s.verifyWorkflowVersioning(tv2, behavior, tv2.Deployment(), nil, nil) } func (s *Versioning3Suite) testCan(crossTq bool, behavior enumspb.VersioningBehavior, upgradeOnCaN bool, expectInherit bool) { From 876e5dfd8446a3c4bb9ef6781038fe9e4980c272 Mon Sep 17 00:00:00 2001 From: Carly de Frondeville Date: Thu, 11 Dec 2025 20:08:10 -0800 Subject: [PATCH 18/19] confirm that once AutoUpgrade wf transitions to v2, it no longer gets CaNSuggested --- tests/versioning_3_test.go | 90 +++++++++++++++++++++++++++++++------- 1 file changed, 75 insertions(+), 15 deletions(-) diff --git a/tests/versioning_3_test.go b/tests/versioning_3_test.go index f45a096625..32dd07f3cf 100644 --- a/tests/versioning_3_test.go +++ b/tests/versioning_3_test.go @@ -2285,26 +2285,86 @@ func (s *Versioning3Suite) testCanWithTaskPoller(behavior enumspb.VersioningBeha } } - return &workflowservice.RespondWorkflowTaskCompletedRequest{ - Commands: []*commandpb.Command{ - { - CommandType: enumspb.COMMAND_TYPE_CONTINUE_AS_NEW_WORKFLOW_EXECUTION, - Attributes: &commandpb.Command_ContinueAsNewWorkflowExecutionCommandAttributes{ - ContinueAsNewWorkflowExecutionCommandAttributes: &commandpb.ContinueAsNewWorkflowExecutionCommandAttributes{ - WorkflowType: tv1.WorkflowType(), - TaskQueue: tv1.TaskQueue(), - Input: tv1.Any().Payloads(), - InitialVersioningBehavior: enumspb.CONTINUE_AS_NEW_VERSIONING_BEHAVIOR_AUTO_UPGRADE, + if behavior == vbPinned { + return &workflowservice.RespondWorkflowTaskCompletedRequest{ + Commands: []*commandpb.Command{ + { + CommandType: enumspb.COMMAND_TYPE_CONTINUE_AS_NEW_WORKFLOW_EXECUTION, + Attributes: &commandpb.Command_ContinueAsNewWorkflowExecutionCommandAttributes{ + ContinueAsNewWorkflowExecutionCommandAttributes: &commandpb.ContinueAsNewWorkflowExecutionCommandAttributes{ + WorkflowType: tv1.WorkflowType(), + TaskQueue: tv1.TaskQueue(), + Input: tv1.Any().Payloads(), + InitialVersioningBehavior: enumspb.CONTINUE_AS_NEW_VERSIONING_BEHAVIOR_AUTO_UPGRADE, + }, }, }, }, - }, - ForceCreateNewWorkflowTask: false, - VersioningBehavior: vbPinned, - DeploymentOptions: tv1.WorkerDeploymentOptions(true), - }, nil + ForceCreateNewWorkflowTask: false, + VersioningBehavior: vbPinned, + DeploymentOptions: tv1.WorkerDeploymentOptions(true), + }, nil + } else { + // For AutoUpgrade, I want to test that once the workflow has transitioned to v2, it doesn't get the CaN suggestion anymore. + return respondEmptyWft(tv2, false, behavior), nil + } }) + if behavior == vbUnpinned { + // Signal the workflow again to trigger another WFT with ContinueAsNewSuggested=false and reasons=[] + _, err = s.FrontendClient().SignalWorkflowExecution(ctx, &workflowservice.SignalWorkflowExecutionRequest{ + Namespace: s.Namespace().String(), + WorkflowExecution: execution, + SignalName: tv1.SignalName(), + Input: tv1.Any().Payloads(), + Identity: tv1.WorkerIdentity(), + }) + s.NoError(err) + s.pollWftAndHandle(tv2, false, nil, + func(task *workflowservice.PollWorkflowTaskQueueResponse) (*workflowservice.RespondWorkflowTaskCompletedRequest, error) { + s.NotNil(task) + + wfTaskStartedEvents := make([]*historypb.HistoryEvent, 0) + for _, event := range task.History.Events { + if event.GetEventType() == enumspb.EVENT_TYPE_WORKFLOW_TASK_STARTED { + wfTaskStartedEvents = append(wfTaskStartedEvents, event) + } + } + s.True(len(wfTaskStartedEvents) > 0) // make sure we are actually verifying non-zero # of events + + for i, event := range wfTaskStartedEvents { + attr := event.GetWorkflowTaskStartedEventAttributes() + // the second-to-last started event should have ContinueAsNewSuggested=true and reasons=[NewTargetVersion] + if i == len(wfTaskStartedEvents)-2 { + s.True(attr.GetSuggestContinueAsNew()) + s.Equal(enumspb.SUGGEST_CONTINUE_AS_NEW_REASON_TARGET_WORKER_DEPLOYMENT_VERSION_CHANGED, attr.GetSuggestContinueAsNewReasons()[0]) + } else { // the other started events should not + s.False(attr.GetSuggestContinueAsNew()) + s.Require().Len(attr.GetSuggestContinueAsNewReasons(), 0) + } + } + + return &workflowservice.RespondWorkflowTaskCompletedRequest{ + Commands: []*commandpb.Command{ + { + CommandType: enumspb.COMMAND_TYPE_CONTINUE_AS_NEW_WORKFLOW_EXECUTION, + Attributes: &commandpb.Command_ContinueAsNewWorkflowExecutionCommandAttributes{ + ContinueAsNewWorkflowExecutionCommandAttributes: &commandpb.ContinueAsNewWorkflowExecutionCommandAttributes{ + WorkflowType: tv1.WorkflowType(), + TaskQueue: tv1.TaskQueue(), + Input: tv1.Any().Payloads(), + InitialVersioningBehavior: enumspb.CONTINUE_AS_NEW_VERSIONING_BEHAVIOR_AUTO_UPGRADE, + }, + }, + }, + }, + ForceCreateNewWorkflowTask: false, + VersioningBehavior: vbUnpinned, + DeploymentOptions: tv2.WorkerDeploymentOptions(true), + }, nil + }) + } + // Start async poller for v2 to receive the ContinueAsNew new run wftNewRunDone := make(chan struct{}) s.pollWftAndHandle(tv2, false, wftNewRunDone, From b8ce3c6bac13cf8ad3d438b5d476c3090a5dc261 Mon Sep 17 00:00:00 2001 From: Carly de Frondeville Date: Fri, 12 Dec 2025 14:08:51 -0800 Subject: [PATCH 19/19] remove commented code --- tests/versioning_3_test.go | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/tests/versioning_3_test.go b/tests/versioning_3_test.go index 32dd07f3cf..f52b09ddea 100644 --- a/tests/versioning_3_test.go +++ b/tests/versioning_3_test.go @@ -108,13 +108,6 @@ func TestVersioning3FunctionalSuite(t *testing.T) { useNewDeploymentData: true, }) }) - //suite.Run(t, &Versioning3Suite{ - // deploymentWorkflowVersion: workerdeployment.AsyncSetCurrentAndRamping, - // useV32: true, - // useNewDeploymentData: true, - // useRevisionNumbers: true, - //}) - } func (s *Versioning3Suite) SetupSuite() { @@ -2155,7 +2148,7 @@ func (s *Versioning3Suite) TestPinnedCaN_upgradeOnCaN_SameTQ() { func (s *Versioning3Suite) TestPinnedCaN_upgradeOnCaN_CrossTQ_Inherit() { s.T().Skip("run after SDK exposes CaN option") - s.testCan(true, vbPinned, true, false) + s.testCan(true, vbPinned, true, true) } func (s *Versioning3Suite) TestPinnedCaN_upgradeOnCaN_CrossTQ_NoInherit() {