Files
tasks/service/history/workflow/mutable_state_rebuilder.go
1e31c76ed1 Versioning cherry-pick for OSS v1.30.2 (#9543)
## What changed?
Cherry-pick versioning PRs
- #9168
  - Cache for system protection
- #9262 
  - Cache for system protection
- #9239 
- Critical PR to enable sending `TargetVersionChanged` flag for
Upgrade-on-CaN feature
- #9147 
- Tracks version drainage properly when version receives workflows via
`VersioningOverride`. Needed for automated worker controllers to
correctly scale versioned workers that received workflows via
`VersioningOverride`.
- #9300 
- Needed for `approximate_backlog_count` metric to track Current and
Ramping version tasks correctly
- #9316 
- Needed for `approximate_backlog_count` metric to track Current and
Ramping version tasks correctly
- #8957 
- Contains minor metric improvement. Included because it adds a test
harness that is used in the two metrics PRs above
- #9250 
  - Bug fix of task rescheduling edge case during AutoUpgrade Transition

## Why?
For OSS v1.30.2

## How did you test it?
- [x] built
- [ ] run locally and tested manually
- [x] covered by existing tests
- [ ] added new unit test(s)
- [ ] added new functional test(s)





---

> [!NOTE]
> **Medium Risk**
> Touches history/matching worker-versioning paths, adding new caches
and changing workflow task/start handling and backlog metric emission;
incorrect caching or signaling could affect dispatch/upgrade behavior
and observability.
> 
> **Overview**
> Adds new worker-versioning protections and upgrade signaling: workflow
task started events now persist a
`workflow_task_target_worker_deployment_version_changed` flag (and emit
a new `workflow_target_version_changed_count` metric) under a new
`EnableSendTargetVersionChanged` dynamic config.
> 
> Introduces two new caches with metrics and dynamic config knobs: a
`RoutingInfoCache` to avoid repeated `GetTaskQueueUserData` lookups
during activity start/transition logic, and a `ReactivationSignalCache`
plus `EnableVersionReactivationSignals` to dedupe and asynchronously
send “reactivation” signals when workflows are pinned (via
start/signal-with-start/reset/update-options) to potentially
drained/inactive worker versions.
> 
> Extends matching backlog metrics to support version-attributed
reporting by adding `BacklogMetricsEmitInterval` and switching queue DB
emission to *physical* backlog gauges (`physical_approximate_backlog_*`)
when attribution is enabled, while keeping legacy gauges when disabled.
> 
> Adds frontend scaffolding for a new visibility RPC `CountSchedules`
(client plumbing, interception/metadata/quota wiring) but leaves the
frontend handler unimplemented, and bumps `go.temporal.io/api` to
`v1.62.2`.
> 
> <sup>Written by [Cursor
Bugbot]() for commit
cb8ae14c86666654c51dd66ae91cb225722e1ba7. This will update automatically
on new commits. Configure
[here]().</sup>

---------

Co-authored-by: Shahab Tajik <[email protected]>
Co-authored-by: Shivam <[email protected]>
2026-03-17 17:19:47 -05:00

768 lines
22 KiB
Go

// Code generated: TODO put <- here to avoid linter, this file need to be rewritten
//go:generate mockgen -package $GOPACKAGE -source $GOFILE -destination mutable_state_rebuilder_mock.go
package workflow
import (
"context"
"github.com/google/uuid"
commonpb "go.temporal.io/api/common/v1"
enumspb "go.temporal.io/api/enums/v1"
historypb "go.temporal.io/api/history/v1"
"go.temporal.io/api/serviceerror"
enumsspb "go.temporal.io/server/api/enums/v1"
"go.temporal.io/server/common/cluster"
"go.temporal.io/server/common/log"
"go.temporal.io/server/common/namespace"
"go.temporal.io/server/common/persistence/versionhistory"
"go.temporal.io/server/common/primitives/timestamp"
"go.temporal.io/server/service/history/historybuilder"
historyi "go.temporal.io/server/service/history/interfaces"
)
type (
MutableStateRebuilder interface {
ApplyEvents(
ctx context.Context,
namespaceID namespace.ID,
requestID string,
execution *commonpb.WorkflowExecution,
history [][]*historypb.HistoryEvent,
newRunHistory []*historypb.HistoryEvent,
newRunID string,
) (historyi.MutableState, error)
}
MutableStateRebuilderImpl struct {
shard historyi.ShardContext
clusterMetadata cluster.Metadata
namespaceRegistry namespace.Registry
logger log.Logger
mutableState historyi.MutableState
}
)
const (
// ErrMessageHistorySizeZero indicate that history is empty
ErrMessageHistorySizeZero = "encounter history size being zero"
)
var _ MutableStateRebuilder = (*MutableStateRebuilderImpl)(nil)
func NewMutableStateRebuilder(
shard historyi.ShardContext,
logger log.Logger,
mutableState historyi.MutableState,
) *MutableStateRebuilderImpl {
return &MutableStateRebuilderImpl{
shard: shard,
clusterMetadata: shard.GetClusterMetadata(),
namespaceRegistry: shard.GetNamespaceRegistry(),
logger: logger,
mutableState: mutableState,
}
}
func (b *MutableStateRebuilderImpl) ApplyEvents(
ctx context.Context,
namespaceID namespace.ID,
requestID string,
execution *commonpb.WorkflowExecution,
history [][]*historypb.HistoryEvent,
newRunHistory []*historypb.HistoryEvent,
newRunID string,
) (historyi.MutableState, error) {
for i := 0; i < len(history)-1; i++ {
_, err := b.applyEvents(ctx, namespaceID, requestID, execution, history[i], nil, "")
if err != nil {
return nil, err
}
}
newMutableState, err := b.applyEvents(ctx, namespaceID, requestID, execution, history[len(history)-1], newRunHistory, newRunID)
if err != nil {
return nil, err
}
// TODO: There doesn't seem to be a good reason to generate tasks here since they'll be generated eventually when we
// close the transaction.
// Previously this comment was here: must generate the activity timer / user timer at the very end
taskGenerator := taskGeneratorProvider.NewTaskGenerator(b.shard, b.mutableState)
if err := taskGenerator.GenerateActivityTimerTasks(); err != nil {
return nil, err
}
if err := taskGenerator.GenerateUserTimerTasks(); err != nil {
return nil, err
}
b.mutableState.SetHistoryBuilder(historybuilder.NewImmutable(history...))
return newMutableState, nil
}
func (b *MutableStateRebuilderImpl) applyEvents(
ctx context.Context,
namespaceID namespace.ID,
requestID string,
execution *commonpb.WorkflowExecution,
history []*historypb.HistoryEvent,
newRunHistory []*historypb.HistoryEvent,
newRunID string,
) (historyi.MutableState, error) {
if len(history) == 0 {
return nil, serviceerror.NewInternal(ErrMessageHistorySizeZero)
}
firstEvent := history[0]
lastEvent := history[len(history)-1]
taskGenerator := taskGeneratorProvider.NewTaskGenerator(b.shard, b.mutableState)
// Need to clear the sticky task queue because workflow turned to passive.
b.mutableState.ClearStickyTaskQueue()
executionInfo := b.mutableState.GetExecutionInfo()
executionInfo.LastFirstEventId = firstEvent.GetEventId()
// Preserve the WorkflowTaskStamp during rebuild to ensure workflow task validation works correctly.
// The stamp is used to invalidate stale workflow tasks and must be maintained across rebuilds.
// Note: The stamp is already persisted in the execution info and should not be reset here.
// NOTE: stateRebuilder is also being used in the active side
if err := b.mutableState.UpdateCurrentVersion(lastEvent.GetVersion(), true); err != nil {
return nil, err
}
versionHistories := executionInfo.GetVersionHistories()
versionHistory, err := versionhistory.GetCurrentVersionHistory(versionHistories)
if err != nil {
return nil, err
}
if err := versionhistory.AddOrUpdateVersionHistoryItem(versionHistory, versionhistory.NewVersionHistoryItem(
lastEvent.GetEventId(),
lastEvent.GetVersion(),
)); err != nil {
return nil, err
}
executionInfo.LastRunningClock = lastEvent.GetTaskId()
for _, event := range history {
switch event.GetEventType() {
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_STARTED:
attributes := event.GetWorkflowExecutionStartedEventAttributes()
// TODO (alex): ParentWorkflowNamespaceId is back filled. Backward compatibility: old event doesn't have ParentNamespaceId set.
if attributes.GetParentWorkflowNamespaceId() == "" && attributes.GetParentWorkflowNamespace() != "" {
parentNamespaceEntry, err := b.namespaceRegistry.GetNamespace(namespace.Name(attributes.GetParentWorkflowNamespace()))
if err != nil {
return nil, err
}
attributes.ParentWorkflowNamespaceId = parentNamespaceEntry.ID().String()
}
if err := b.mutableState.ApplyWorkflowExecutionStartedEvent(
nil, // shard clock is local to cluster
execution,
requestID,
event,
); err != nil {
return nil, err
}
if err := taskGenerator.GenerateRecordWorkflowStartedTasks(
event,
); err != nil {
return nil, err
}
var err error
executionInfo.WorkflowExecutionTimerTaskStatus, err = taskGenerator.GenerateWorkflowStartTasks(
event,
)
if err != nil {
return nil, err
}
if timestamp.DurationValue(attributes.GetFirstWorkflowTaskBackoff()) > 0 {
if err := taskGenerator.GenerateDelayedWorkflowTasks(
event,
); err != nil {
return nil, err
}
}
if err := b.mutableState.SetHistoryTree(
executionInfo.WorkflowExecutionTimeout,
executionInfo.WorkflowRunTimeout,
execution.GetRunId(),
); err != nil {
return nil, err
}
case enumspb.EVENT_TYPE_WORKFLOW_TASK_SCHEDULED:
attributes := event.GetWorkflowTaskScheduledEventAttributes()
// use event.GetEventTime() as WorkflowTaskOriginalScheduledTimestamp, because the heartbeat is not happening here.
// The WorkflowTaskStamp will be preserved from the mutable state's execution info during task generation.
workflowTask, err := b.mutableState.ApplyWorkflowTaskScheduledEvent(
event.GetVersion(),
event.GetEventId(),
attributes.TaskQueue,
attributes.GetStartToCloseTimeout(),
attributes.GetAttempt(),
event.GetEventTime(),
event.GetEventTime(),
enumsspb.WORKFLOW_TASK_TYPE_NORMAL, // speculative workflow tasks are not replicated.
)
if err != nil {
return nil, err
}
// since we do not use stickiness on the standby side
// there shall be no workflowTask schedule to start timeout
// NOTE: at the beginning of the loop, stickyness is cleared
if err := taskGenerator.GenerateScheduleWorkflowTaskTasks(
workflowTask.ScheduledEventID,
); err != nil {
return nil, err
}
case enumspb.EVENT_TYPE_WORKFLOW_TASK_STARTED:
attributes := event.GetWorkflowTaskStartedEventAttributes()
workflowTask, err := b.mutableState.ApplyWorkflowTaskStartedEvent(
nil,
event.GetVersion(),
attributes.GetScheduledEventId(),
event.GetEventId(),
attributes.GetRequestId(),
timestamp.TimeValue(event.GetEventTime()),
attributes.GetSuggestContinueAsNew(),
attributes.GetHistorySizeBytes(),
attributes.GetWorkerVersion(),
attributes.GetBuildIdRedirectCounter(),
attributes.GetSuggestContinueAsNewReasons(),
attributes.GetTargetWorkerDeploymentVersionChanged(),
)
if err != nil {
return nil, err
}
if err := taskGenerator.GenerateStartWorkflowTaskTasks(
workflowTask.ScheduledEventID,
); err != nil {
return nil, err
}
case enumspb.EVENT_TYPE_WORKFLOW_TASK_COMPLETED:
if err := b.mutableState.ApplyWorkflowTaskCompletedEvent(
event,
); err != nil {
return nil, err
}
case enumspb.EVENT_TYPE_WORKFLOW_TASK_TIMED_OUT:
if err := b.mutableState.ApplyWorkflowTaskTimedOutEvent(
event.GetWorkflowTaskTimedOutEventAttributes().GetTimeoutType(),
); err != nil {
return nil, err
}
// this is for transient workflowTask
workflowTask, err := b.mutableState.ApplyTransientWorkflowTaskScheduled()
if err != nil {
return nil, err
}
if workflowTask != nil {
// since we do not use stickiness on the standby side
// there shall be no workflowTask schedule to start timeout
// NOTE: at the beginning of the loop, stickyness is cleared
if err := taskGenerator.GenerateScheduleWorkflowTaskTasks(
workflowTask.ScheduledEventID,
); err != nil {
return nil, err
}
}
case enumspb.EVENT_TYPE_WORKFLOW_TASK_FAILED:
if err := b.mutableState.ApplyWorkflowTaskFailedEvent(); err != nil {
return nil, err
}
// this is for transient workflowTask
workflowTask, err := b.mutableState.ApplyTransientWorkflowTaskScheduled()
if err != nil {
return nil, err
}
if workflowTask != nil {
// since we do not use stickiness on the standby side
// there shall be no workflowTask schedule to start timeout
// NOTE: at the beginning of the loop, stickyness is cleared
if err := taskGenerator.GenerateScheduleWorkflowTaskTasks(
workflowTask.ScheduledEventID,
); err != nil {
return nil, err
}
}
case enumspb.EVENT_TYPE_ACTIVITY_TASK_SCHEDULED:
if _, err := b.mutableState.ApplyActivityTaskScheduledEvent(
firstEvent.GetEventId(),
event,
); err != nil {
return nil, err
}
if err := taskGenerator.GenerateActivityTasks(
event.GetEventId(),
); err != nil {
return nil, err
}
case enumspb.EVENT_TYPE_ACTIVITY_TASK_STARTED:
if err := b.mutableState.ApplyActivityTaskStartedEvent(
event,
); err != nil {
return nil, err
}
case enumspb.EVENT_TYPE_ACTIVITY_TASK_COMPLETED:
if err := b.mutableState.ApplyActivityTaskCompletedEvent(
event,
); err != nil {
return nil, err
}
case enumspb.EVENT_TYPE_ACTIVITY_TASK_FAILED:
if err := b.mutableState.ApplyActivityTaskFailedEvent(
event,
); err != nil {
return nil, err
}
case enumspb.EVENT_TYPE_ACTIVITY_TASK_TIMED_OUT:
if err := b.mutableState.ApplyActivityTaskTimedOutEvent(
event,
); err != nil {
return nil, err
}
case enumspb.EVENT_TYPE_ACTIVITY_TASK_CANCEL_REQUESTED:
if err := b.mutableState.ApplyActivityTaskCancelRequestedEvent(
event,
); err != nil {
return nil, err
}
case enumspb.EVENT_TYPE_ACTIVITY_TASK_CANCELED:
if err := b.mutableState.ApplyActivityTaskCanceledEvent(
event,
); err != nil {
return nil, err
}
case enumspb.EVENT_TYPE_TIMER_STARTED:
if _, err := b.mutableState.ApplyTimerStartedEvent(
event,
); err != nil {
return nil, err
}
case enumspb.EVENT_TYPE_TIMER_FIRED:
if err := b.mutableState.ApplyTimerFiredEvent(
event,
); err != nil {
return nil, err
}
case enumspb.EVENT_TYPE_TIMER_CANCELED:
if err := b.mutableState.ApplyTimerCanceledEvent(
event,
); err != nil {
return nil, err
}
case enumspb.EVENT_TYPE_START_CHILD_WORKFLOW_EXECUTION_INITIATED:
if _, err := b.mutableState.ApplyStartChildWorkflowExecutionInitiatedEvent(
firstEvent.GetEventId(),
event,
); err != nil {
return nil, err
}
if err := taskGenerator.GenerateChildWorkflowTasks(
event.GetEventId(),
); err != nil {
return nil, err
}
case enumspb.EVENT_TYPE_START_CHILD_WORKFLOW_EXECUTION_FAILED:
if err := b.mutableState.ApplyStartChildWorkflowExecutionFailedEvent(
event,
); err != nil {
return nil, err
}
case enumspb.EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_STARTED:
if err := b.mutableState.ApplyChildWorkflowExecutionStartedEvent(
event,
nil, // shard clock is local to cluster
); err != nil {
return nil, err
}
case enumspb.EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_COMPLETED:
if err := b.mutableState.ApplyChildWorkflowExecutionCompletedEvent(
event,
); err != nil {
return nil, err
}
case enumspb.EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_FAILED:
if err := b.mutableState.ApplyChildWorkflowExecutionFailedEvent(
event,
); err != nil {
return nil, err
}
case enumspb.EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_CANCELED:
if err := b.mutableState.ApplyChildWorkflowExecutionCanceledEvent(
event,
); err != nil {
return nil, err
}
case enumspb.EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_TIMED_OUT:
if err := b.mutableState.ApplyChildWorkflowExecutionTimedOutEvent(
event,
); err != nil {
return nil, err
}
case enumspb.EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_TERMINATED:
if err := b.mutableState.ApplyChildWorkflowExecutionTerminatedEvent(
event,
); err != nil {
return nil, err
}
case enumspb.EVENT_TYPE_REQUEST_CANCEL_EXTERNAL_WORKFLOW_EXECUTION_INITIATED:
if _, err := b.mutableState.ApplyRequestCancelExternalWorkflowExecutionInitiatedEvent(
firstEvent.GetEventId(),
event,
// create a new request ID which is used by transfer queue processor
// if namespace is failed over at this point
uuid.NewString(),
); err != nil {
return nil, err
}
if err := taskGenerator.GenerateRequestCancelExternalTasks(
event,
); err != nil {
return nil, err
}
case enumspb.EVENT_TYPE_REQUEST_CANCEL_EXTERNAL_WORKFLOW_EXECUTION_FAILED:
if err := b.mutableState.ApplyRequestCancelExternalWorkflowExecutionFailedEvent(
event,
); err != nil {
return nil, err
}
case enumspb.EVENT_TYPE_EXTERNAL_WORKFLOW_EXECUTION_CANCEL_REQUESTED:
if err := b.mutableState.ApplyExternalWorkflowExecutionCancelRequested(
event,
); err != nil {
return nil, err
}
case enumspb.EVENT_TYPE_SIGNAL_EXTERNAL_WORKFLOW_EXECUTION_INITIATED:
// Create a new request ID which is used by transfer queue processor if namespace is failed over at this point
signalRequestID := uuid.NewString()
if _, err := b.mutableState.ApplySignalExternalWorkflowExecutionInitiatedEvent(
firstEvent.GetEventId(),
event,
signalRequestID,
); err != nil {
return nil, err
}
if err := taskGenerator.GenerateSignalExternalTasks(
event,
); err != nil {
return nil, err
}
case enumspb.EVENT_TYPE_SIGNAL_EXTERNAL_WORKFLOW_EXECUTION_FAILED:
if err := b.mutableState.ApplySignalExternalWorkflowExecutionFailedEvent(
event,
); err != nil {
return nil, err
}
case enumspb.EVENT_TYPE_EXTERNAL_WORKFLOW_EXECUTION_SIGNALED:
if err := b.mutableState.ApplyExternalWorkflowExecutionSignaled(
event,
); err != nil {
return nil, err
}
case enumspb.EVENT_TYPE_MARKER_RECORDED:
// No mutable state action is needed
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_SIGNALED:
if err := b.mutableState.ApplyWorkflowExecutionSignaled(
event,
); err != nil {
return nil, err
}
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_CANCEL_REQUESTED:
if err := b.mutableState.ApplyWorkflowExecutionCancelRequestedEvent(
event,
); err != nil {
return nil, err
}
case enumspb.EVENT_TYPE_UPSERT_WORKFLOW_SEARCH_ATTRIBUTES:
b.mutableState.ApplyUpsertWorkflowSearchAttributesEvent(event)
if err := taskGenerator.GenerateUpsertVisibilityTask(); err != nil {
return nil, err
}
case enumspb.EVENT_TYPE_WORKFLOW_PROPERTIES_MODIFIED:
b.mutableState.ApplyWorkflowPropertiesModifiedEvent(event)
if err := taskGenerator.GenerateUpsertVisibilityTask(); err != nil {
return nil, err
}
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_COMPLETED:
if err := b.mutableState.ApplyWorkflowExecutionCompletedEvent(
firstEvent.GetEventId(),
event,
); err != nil {
return nil, err
}
if err := taskGenerator.GenerateWorkflowCloseTasks(
event.GetEventTime().AsTime(),
false,
false, // skipCloseTransferTask
); err != nil {
return nil, err
}
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_FAILED:
if err := b.mutableState.ApplyWorkflowExecutionFailedEvent(
firstEvent.GetEventId(),
event,
); err != nil {
return nil, err
}
if err := taskGenerator.GenerateWorkflowCloseTasks(
event.GetEventTime().AsTime(),
false,
false, // skipCloseTransferTask
); err != nil {
return nil, err
}
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_TIMED_OUT:
if err := b.mutableState.ApplyWorkflowExecutionTimedoutEvent(
firstEvent.GetEventId(),
event,
); err != nil {
return nil, err
}
if err := taskGenerator.GenerateWorkflowCloseTasks(
event.GetEventTime().AsTime(),
false,
false, // skipCloseTransferTask
); err != nil {
return nil, err
}
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_CANCELED:
if err := b.mutableState.ApplyWorkflowExecutionCanceledEvent(
firstEvent.GetEventId(),
event,
); err != nil {
return nil, err
}
if err := taskGenerator.GenerateWorkflowCloseTasks(
event.GetEventTime().AsTime(),
false,
false, // skipCloseTransferTask
); err != nil {
return nil, err
}
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_TERMINATED:
if err := b.mutableState.ApplyWorkflowExecutionTerminatedEvent(
firstEvent.GetEventId(),
event,
); err != nil {
return nil, err
}
if err := taskGenerator.GenerateWorkflowCloseTasks(
event.GetEventTime().AsTime(),
false,
false, // skipCloseTransferTask
); err != nil {
return nil, err
}
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_CONTINUED_AS_NEW:
// This is for backward compatibility, old replication tasks generated for continuedAsNew case
// doesn't have newRunID field set. In that case, we need to get newRunID from the last event.
continuedAsNewRunID := event.GetWorkflowExecutionContinuedAsNewEventAttributes().GetNewExecutionRunId()
if newRunID == "" {
newRunID = continuedAsNewRunID
} else if newRunID != continuedAsNewRunID {
return nil, serviceerror.NewInternalf(
"ApplyEvents encounted newRunID mismatch for continuedAsNew event, task newRunID: %v, event newRunID: %v",
newRunID,
continuedAsNewRunID,
)
}
if err := b.mutableState.ApplyWorkflowExecutionContinuedAsNewEvent(
firstEvent.GetEventId(),
event,
); err != nil {
return nil, err
}
if err := taskGenerator.GenerateWorkflowCloseTasks(
event.GetEventTime().AsTime(),
false,
false, // skipCloseTransferTask
); err != nil {
return nil, err
}
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_UPDATE_ADMITTED:
if err := b.mutableState.ApplyWorkflowExecutionUpdateAdmittedEvent(event, firstEvent.GetEventId()); err != nil {
return nil, err
}
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_UPDATE_ACCEPTED:
if err := b.mutableState.ApplyWorkflowExecutionUpdateAcceptedEvent(event); err != nil {
return nil, err
}
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_UPDATE_COMPLETED:
if err := b.mutableState.ApplyWorkflowExecutionUpdateCompletedEvent(event, firstEvent.GetEventId()); err != nil {
return nil, err
}
case enumspb.EVENT_TYPE_ACTIVITY_PROPERTIES_MODIFIED_EXTERNALLY,
enumspb.EVENT_TYPE_WORKFLOW_PROPERTIES_MODIFIED_EXTERNALLY:
return nil, serviceerror.NewUnimplemented("Workflow/activity property modification not implemented")
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_OPTIONS_UPDATED:
if err := b.mutableState.ApplyWorkflowExecutionOptionsUpdatedEvent(event); err != nil {
return nil, err
}
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_PAUSED:
if err := b.mutableState.ApplyWorkflowExecutionPausedEvent(event); err != nil {
return nil, err
}
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_UNPAUSED:
if err := b.mutableState.ApplyWorkflowExecutionUnpausedEvent(event); err != nil {
return nil, err
}
default:
def, ok := b.shard.StateMachineRegistry().EventDefinition(event.GetEventType())
if !ok {
return nil, serviceerror.NewInvalidArgumentf("Unknown event type: %v", event.GetEventType())
}
if err := def.Apply(b.mutableState.HSM(), event); err != nil {
return nil, err
}
}
}
// The length of newRunHistory can be zero in resend case
if len(newRunHistory) == 0 {
return nil, nil
}
if b.mutableState.GetExecutionState().Status == enumspb.WORKFLOW_EXECUTION_STATUS_RUNNING {
return nil, serviceerror.NewInternal("Cannot apply events for new run when current run is still running")
}
return b.applyNewRunHistory(
ctx,
namespaceID,
&commonpb.WorkflowExecution{
WorkflowId: execution.WorkflowId,
RunId: newRunID,
},
newRunHistory,
)
}
func (b *MutableStateRebuilderImpl) applyNewRunHistory(
ctx context.Context,
namespaceID namespace.ID,
newExecution *commonpb.WorkflowExecution,
newRunHistory []*historypb.HistoryEvent,
) (historyi.MutableState, error) {
// TODO: replication task should contain enough information to determine whether the new run is part of the same chain
// and not relying on a specific event type to make that decision
sameWorkflowChain := false
newRunFirstEvent := newRunHistory[0]
if newRunFirstEvent.GetEventType() == enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_STARTED {
newRunFirstRunID := newRunFirstEvent.GetWorkflowExecutionStartedEventAttributes().FirstExecutionRunId
sameWorkflowChain = newRunFirstRunID == b.mutableState.GetExecutionInfo().FirstExecutionRunId
}
var err error
var newRunMutableState historyi.MutableState
if sameWorkflowChain {
newRunMutableState, err = NewMutableStateInChain(
b.shard,
b.shard.GetEventsCache(),
b.logger,
b.mutableState.GetNamespaceEntry(),
newExecution.WorkflowId,
newExecution.RunId,
timestamp.TimeValue(newRunHistory[0].GetEventTime()),
b.mutableState,
)
if err != nil {
return nil, err
}
} else {
newRunMutableState = NewMutableState(
b.shard,
b.shard.GetEventsCache(),
b.logger,
b.mutableState.GetNamespaceEntry(),
newExecution.WorkflowId,
newExecution.RunId,
timestamp.TimeValue(newRunHistory[0].GetEventTime()),
)
}
newRunStateBuilder := NewMutableStateRebuilder(b.shard, b.logger, newRunMutableState)
_, err = newRunStateBuilder.ApplyEvents(
ctx,
namespaceID,
uuid.NewString(),
newExecution,
[][]*historypb.HistoryEvent{newRunHistory},
nil,
"",
)
if err != nil {
return nil, err
}
return newRunMutableState, nil
}