Files
tasks/service/history/workflow/mutable_state_impl.go
Stephan Behnke 2d0e892384 Drop scheduled tasks if duration too long (#6124)
## What changed?
<!-- Describe what has changed in this PR -->
- Drop scheduled tasks if it's scheduled to run far in the future (
duration > 99 year )
- What if we want to recover those timers?
  - Do a namespace migration, that will re-create all the timers.

## Why?
<!-- Tell your future self why have you made these changes -->
- There's no point creating those timers. They just consume storage
spaces. Persistence implementations also don't have a good way for
handling them.
- #4775 capped all timer
settings to MaxAllowTimer for new workflows. But if workflows are
created before that check is added, we may still create timers for those
large durations. E.g. the new logic added for creating workflow
execution timeout timer based on the persisted workflow execution
expiration time.
- NOTE: all capped timers will be dropped as well.

## How did you test it?
<!-- How have you verified this change? Tested locally? Added a unit
test? Checked in staging env? -->
- Added unit test

## Potential risks
<!-- Assuming the worst case, what can be broken when deploying this
change to production? -->

## Documentation
<!-- Have you made sure this change doesn't falsify anything currently
stated in `docs/`? If significant
new behavior is added, have you described that in `docs/`? -->

## Is hotfix candidate?
<!-- Is this PR a hotfix candidate or does it require a notification to
be sent to the broader community? (Yes/No) -->
- Yes
2024-06-13 18:01:44 -07:00

5779 lines
191 KiB
Go

// The MIT License
//
// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved.
//
// Copyright (c) 2020 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package workflow
import (
"cmp"
"context"
"fmt"
"math/rand"
"time"
"github.com/nexus-rpc/sdk-go/nexus"
"github.com/pborman/uuid"
commandpb "go.temporal.io/api/command/v1"
commonpb "go.temporal.io/api/common/v1"
enumspb "go.temporal.io/api/enums/v1"
failurepb "go.temporal.io/api/failure/v1"
"go.temporal.io/api/history/v1"
historypb "go.temporal.io/api/history/v1"
"go.temporal.io/api/serviceerror"
taskqueuepb "go.temporal.io/api/taskqueue/v1"
updatepb "go.temporal.io/api/update/v1"
workflowpb "go.temporal.io/api/workflow/v1"
"go.temporal.io/api/workflowservice/v1"
"golang.org/x/exp/slices"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/durationpb"
"google.golang.org/protobuf/types/known/timestamppb"
clockspb "go.temporal.io/server/api/clock/v1"
enumsspb "go.temporal.io/server/api/enums/v1"
historyspb "go.temporal.io/server/api/history/v1"
"go.temporal.io/server/api/historyservice/v1"
persistencespb "go.temporal.io/server/api/persistence/v1"
tokenspb "go.temporal.io/server/api/token/v1"
updatespb "go.temporal.io/server/api/update/v1"
workflowspb "go.temporal.io/server/api/workflow/v1"
"go.temporal.io/server/common"
"go.temporal.io/server/common/backoff"
"go.temporal.io/server/common/clock"
"go.temporal.io/server/common/cluster"
"go.temporal.io/server/common/convert"
"go.temporal.io/server/common/definition"
"go.temporal.io/server/common/enums"
"go.temporal.io/server/common/failure"
"go.temporal.io/server/common/log"
"go.temporal.io/server/common/log/tag"
"go.temporal.io/server/common/metrics"
"go.temporal.io/server/common/namespace"
commonnexus "go.temporal.io/server/common/nexus"
"go.temporal.io/server/common/payload"
"go.temporal.io/server/common/persistence"
"go.temporal.io/server/common/persistence/versionhistory"
"go.temporal.io/server/common/primitives/timestamp"
"go.temporal.io/server/common/searchattribute"
"go.temporal.io/server/common/util"
"go.temporal.io/server/common/worker_versioning"
"go.temporal.io/server/components/callbacks"
"go.temporal.io/server/service/history/configs"
"go.temporal.io/server/service/history/consts"
"go.temporal.io/server/service/history/events"
"go.temporal.io/server/service/history/historybuilder"
"go.temporal.io/server/service/history/hsm"
"go.temporal.io/server/service/history/shard"
"go.temporal.io/server/service/history/tasks"
)
const (
emptyUUID = "emptyUuid"
mutableStateInvalidHistoryActionMsg = "invalid history builder state for action"
mutableStateInvalidHistoryActionMsgTemplate = mutableStateInvalidHistoryActionMsg + ": %v, %v"
int64SizeBytes = 8
)
var (
// Scheduled tasks with timestamp after this will not be created.
// Those tasks are too far in the future and pratically never fire and just consume storage space.
// NOTE: this value is less than timer.MaxAllowedTimer so that no capped timers will be created.
maxScheduledTaskDuration = time.Hour * 24 * 365 * 99
)
var (
// ErrWorkflowFinished indicates trying to mutate mutable state after workflow finished
ErrWorkflowFinished = serviceerror.NewInternal("invalid mutable state action: mutation after finish")
// ErrMissingTimerInfo indicates missing timer info
ErrMissingTimerInfo = serviceerror.NewInternal("unable to get timer info")
// ErrMissingActivityInfo indicates missing activity info
ErrMissingActivityInfo = serviceerror.NewInternal("unable to get activity info")
// ErrMissingChildWorkflowInfo indicates missing child workflow info
ErrMissingChildWorkflowInfo = serviceerror.NewInternal("unable to get child workflow info")
// ErrMissingRequestCancelInfo indicates missing request cancel info
ErrMissingRequestCancelInfo = serviceerror.NewInternal("unable to get request cancel info")
// ErrMissingSignalInfo indicates missing signal external
ErrMissingSignalInfo = serviceerror.NewInternal("unable to get signal info")
// ErrMissingWorkflowStartEvent indicates missing workflow start event
ErrMissingWorkflowStartEvent = serviceerror.NewInternal("unable to get workflow start event")
// ErrMissingWorkflowCompletionEvent indicates missing workflow completion event
ErrMissingWorkflowCompletionEvent = serviceerror.NewInternal("unable to get workflow completion event")
// ErrMissingActivityScheduledEvent indicates missing workflow activity scheduled event
ErrMissingActivityScheduledEvent = serviceerror.NewInternal("unable to get activity scheduled event")
// ErrMissingChildWorkflowInitiatedEvent indicates missing child workflow initiated event
ErrMissingChildWorkflowInitiatedEvent = serviceerror.NewInternal("unable to get child workflow initiated event")
// ErrMissingSignalInitiatedEvent indicates missing workflow signal initiated event
ErrMissingSignalInitiatedEvent = serviceerror.NewInternal("unable to get signal initiated event")
timeZeroUTC = time.Unix(0, 0).UTC()
)
type (
MutableStateImpl struct {
pendingActivityTimerHeartbeats map[int64]time.Time // Scheduled Event ID -> LastHeartbeatTimeoutVisibilityInSeconds.
pendingActivityInfoIDs map[int64]*persistencespb.ActivityInfo // Scheduled Event ID -> Activity Info.
pendingActivityIDToEventID map[string]int64 // Activity ID -> Scheduled Event ID of the activity.
updateActivityInfos map[int64]*persistencespb.ActivityInfo // Modified activities from last update.
deleteActivityInfos map[int64]struct{} // Deleted activities from last update.
syncActivityTasks map[int64]struct{} // Activity to be sync to remote
pendingTimerInfoIDs map[string]*persistencespb.TimerInfo // User Timer ID -> Timer Info.
pendingTimerEventIDToID map[int64]string // User Timer Start Event ID -> User Timer ID.
updateTimerInfos map[string]*persistencespb.TimerInfo // Modified timers from last update.
deleteTimerInfos map[string]struct{} // Deleted timers from last update.
pendingChildExecutionInfoIDs map[int64]*persistencespb.ChildExecutionInfo // Initiated Event ID -> Child Execution Info
updateChildExecutionInfos map[int64]*persistencespb.ChildExecutionInfo // Modified ChildExecution Infos since last update
deleteChildExecutionInfos map[int64]struct{} // Deleted ChildExecution Info since last update
pendingRequestCancelInfoIDs map[int64]*persistencespb.RequestCancelInfo // Initiated Event ID -> RequestCancelInfo
updateRequestCancelInfos map[int64]*persistencespb.RequestCancelInfo // Modified RequestCancel Infos since last update, for persistence update
deleteRequestCancelInfos map[int64]struct{} // Deleted RequestCancel Info since last update, for persistence update
pendingSignalInfoIDs map[int64]*persistencespb.SignalInfo // Initiated Event ID -> SignalInfo
updateSignalInfos map[int64]*persistencespb.SignalInfo // Modified SignalInfo since last update
deleteSignalInfos map[int64]struct{} // Deleted SignalInfo since last update
pendingSignalRequestedIDs map[string]struct{} // Set of signaled requestIds
updateSignalRequestedIDs map[string]struct{} // Set of signaled requestIds since last update
deleteSignalRequestedIDs map[string]struct{} // Deleted signaled requestId
executionInfo *persistencespb.WorkflowExecutionInfo // Workflow mutable state info.
executionState *persistencespb.WorkflowExecutionState
hBuilder *historybuilder.HistoryBuilder
// In-memory only attributes
currentVersion int64
// The namespace failover version at the time the current transaction started.
// Cached for consistency across a single transaction.
// This value can be used to mutate sub-statemachines after the workflow has already been closed.
currentTransactionNamespaceFailoverVersion int64
// Running approximate total size of mutable state fields (except buffered events) when written to DB in bytes.
// Buffered events are added to this value when calling GetApproximatePersistedSize.
approximateSize int
// Buffer events from DB
bufferEventsInDB []*historypb.HistoryEvent
// Indicates the workflow state in DB, can be used to calculate
// whether this workflow is pointed by current workflow record.
stateInDB enumsspb.WorkflowExecutionState
// TODO deprecate nextEventIDInDB in favor of dbRecordVersion.
// Indicates the next event ID in DB, for conditional update.
nextEventIDInDB int64
// Indicates the DB record version, for conditional update.
dbRecordVersion int64
// Namespace entry contains a snapshot of namespace.
// NOTE: do not use the failover version inside, use currentVersion above.
namespaceEntry *namespace.Namespace
// Record if an event has been applied to mutable state.
// TODO: persist this to db
appliedEvents map[string]struct{}
// A flag indicating if workflow has attempted to close (complete/cancel/continue as new)
// but failed due to undelivered buffered events.
// The flag will be unset whenever workflow task successfully completed, timedout or failed
// due to cause other than UnhandledCommand.
workflowCloseAttempted bool
InsertTasks map[tasks.Category][]tasks.Task
speculativeWorkflowTaskTimeoutTask *tasks.WorkflowTaskTimeoutTask
// Do not rely on this, this is only updated on
// Load() and closeTransactionXXX methods. So when
// a transaction is in progress, this value will be
// wrong. This exists primarily for visibility via CLI.
checksum *persistencespb.Checksum
taskGenerator TaskGenerator
workflowTaskManager *workflowTaskStateMachine
QueryRegistry QueryRegistry
shard shard.Context
clusterMetadata cluster.Metadata
eventsCache events.Cache
config *configs.Config
timeSource clock.TimeSource
logger log.Logger
metricsHandler metrics.Handler
stateMachineNode *hsm.Node
// Tracks all events added via the AddHistoryEvent method that is used by the state machine framework.
currentTransactionAddedStateMachineEventTypes []enumspb.EventType
}
)
var _ MutableState = (*MutableStateImpl)(nil)
func NewMutableState(
shard shard.Context,
eventsCache events.Cache,
logger log.Logger,
namespaceEntry *namespace.Namespace,
workflowID string,
runID string,
startTime time.Time,
) *MutableStateImpl {
s := &MutableStateImpl{
updateActivityInfos: make(map[int64]*persistencespb.ActivityInfo),
pendingActivityTimerHeartbeats: make(map[int64]time.Time),
pendingActivityInfoIDs: make(map[int64]*persistencespb.ActivityInfo),
pendingActivityIDToEventID: make(map[string]int64),
deleteActivityInfos: make(map[int64]struct{}),
syncActivityTasks: make(map[int64]struct{}),
pendingTimerInfoIDs: make(map[string]*persistencespb.TimerInfo),
pendingTimerEventIDToID: make(map[int64]string),
updateTimerInfos: make(map[string]*persistencespb.TimerInfo),
deleteTimerInfos: make(map[string]struct{}),
updateChildExecutionInfos: make(map[int64]*persistencespb.ChildExecutionInfo),
pendingChildExecutionInfoIDs: make(map[int64]*persistencespb.ChildExecutionInfo),
deleteChildExecutionInfos: make(map[int64]struct{}),
updateRequestCancelInfos: make(map[int64]*persistencespb.RequestCancelInfo),
pendingRequestCancelInfoIDs: make(map[int64]*persistencespb.RequestCancelInfo),
deleteRequestCancelInfos: make(map[int64]struct{}),
updateSignalInfos: make(map[int64]*persistencespb.SignalInfo),
pendingSignalInfoIDs: make(map[int64]*persistencespb.SignalInfo),
deleteSignalInfos: make(map[int64]struct{}),
updateSignalRequestedIDs: make(map[string]struct{}),
pendingSignalRequestedIDs: make(map[string]struct{}),
deleteSignalRequestedIDs: make(map[string]struct{}),
approximateSize: 0,
currentVersion: namespaceEntry.FailoverVersion(),
bufferEventsInDB: nil,
stateInDB: enumsspb.WORKFLOW_EXECUTION_STATE_VOID,
nextEventIDInDB: common.FirstEventID,
dbRecordVersion: 1,
namespaceEntry: namespaceEntry,
appliedEvents: make(map[string]struct{}),
InsertTasks: make(map[tasks.Category][]tasks.Task),
QueryRegistry: NewQueryRegistry(),
shard: shard,
clusterMetadata: shard.GetClusterMetadata(),
eventsCache: eventsCache,
config: shard.GetConfig(),
timeSource: shard.GetTimeSource(),
logger: logger,
metricsHandler: shard.GetMetricsHandler().WithTags(metrics.OperationTag(metrics.WorkflowContextScope)),
}
s.executionInfo = &persistencespb.WorkflowExecutionInfo{
NamespaceId: namespaceEntry.ID().String(),
WorkflowId: workflowID,
WorkflowTaskVersion: common.EmptyVersion,
WorkflowTaskScheduledEventId: common.EmptyEventID,
WorkflowTaskStartedEventId: common.EmptyEventID,
WorkflowTaskRequestId: emptyUUID,
WorkflowTaskTimeout: timestamp.DurationFromSeconds(0),
WorkflowTaskAttempt: 1,
LastWorkflowTaskStartedEventId: common.EmptyEventID,
StartTime: timestamppb.New(startTime),
VersionHistories: versionhistory.NewVersionHistories(&historyspb.VersionHistory{}),
ExecutionStats: &persistencespb.ExecutionStats{HistorySize: 0},
SubStateMachinesByType: make(map[int32]*persistencespb.StateMachineMap),
}
s.approximateSize += s.executionInfo.Size()
s.executionState = &persistencespb.WorkflowExecutionState{
RunId: runID,
State: enumsspb.WORKFLOW_EXECUTION_STATE_CREATED,
Status: enumspb.WORKFLOW_EXECUTION_STATUS_RUNNING,
}
s.approximateSize += s.executionState.Size()
s.hBuilder = historybuilder.New(
s.timeSource,
s.shard.GenerateTaskIDs,
s.currentVersion,
common.FirstEventID,
s.bufferEventsInDB,
s.metricsHandler,
)
s.taskGenerator = taskGeneratorProvider.NewTaskGenerator(shard, s)
s.workflowTaskManager = newWorkflowTaskStateMachine(s)
s.mustInitHSM()
return s
}
func NewMutableStateFromDB(
shard shard.Context,
eventsCache events.Cache,
logger log.Logger,
namespaceEntry *namespace.Namespace,
dbRecord *persistencespb.WorkflowMutableState,
dbRecordVersion int64,
) (*MutableStateImpl, error) {
// startTime will be overridden by DB record
startTime := time.Time{}
mutableState := NewMutableState(
shard,
eventsCache,
logger,
namespaceEntry,
dbRecord.ExecutionInfo.WorkflowId,
dbRecord.ExecutionState.RunId,
startTime,
)
if dbRecord.ActivityInfos != nil {
mutableState.pendingActivityInfoIDs = dbRecord.ActivityInfos
mutableState.approximateSize += int64SizeBytes * len(mutableState.pendingActivityInfoIDs)
}
for _, activityInfo := range dbRecord.ActivityInfos {
mutableState.pendingActivityIDToEventID[activityInfo.ActivityId] = activityInfo.ScheduledEventId
mutableState.approximateSize += activityInfo.Size()
if (activityInfo.TimerTaskStatus & TimerTaskStatusCreatedHeartbeat) > 0 {
// Sets last pending timer heartbeat to year 2000.
// This ensures at least one heartbeat task will be processed for the pending activity.
mutableState.pendingActivityTimerHeartbeats[activityInfo.ScheduledEventId] = time.Unix(946684800, 0)
}
}
if dbRecord.TimerInfos != nil {
mutableState.pendingTimerInfoIDs = dbRecord.TimerInfos
}
for timerID, timerInfo := range dbRecord.TimerInfos {
mutableState.pendingTimerEventIDToID[timerInfo.GetStartedEventId()] = timerInfo.GetTimerId()
mutableState.approximateSize += timerInfo.Size()
mutableState.approximateSize += len(timerID)
}
if dbRecord.ChildExecutionInfos != nil {
mutableState.pendingChildExecutionInfoIDs = dbRecord.ChildExecutionInfos
mutableState.approximateSize += int64SizeBytes * len(mutableState.pendingChildExecutionInfoIDs)
}
for _, childInfo := range dbRecord.ChildExecutionInfos {
mutableState.approximateSize += childInfo.Size()
}
if dbRecord.RequestCancelInfos != nil {
mutableState.pendingRequestCancelInfoIDs = dbRecord.RequestCancelInfos
mutableState.approximateSize += int64SizeBytes * len(mutableState.pendingRequestCancelInfoIDs)
}
for _, cancelInfo := range dbRecord.RequestCancelInfos {
mutableState.approximateSize += cancelInfo.Size()
}
if dbRecord.SignalInfos != nil {
mutableState.pendingSignalInfoIDs = dbRecord.SignalInfos
mutableState.approximateSize += int64SizeBytes * len(mutableState.pendingSignalInfoIDs)
}
for _, signalInfo := range dbRecord.SignalInfos {
mutableState.approximateSize += signalInfo.Size()
}
mutableState.pendingSignalRequestedIDs = convert.StringSliceToSet(dbRecord.SignalRequestedIds)
for requestID := range mutableState.pendingSignalRequestedIDs {
mutableState.approximateSize += len(requestID)
}
mutableState.approximateSize += dbRecord.ExecutionState.Size() - mutableState.executionState.Size()
mutableState.executionState = dbRecord.ExecutionState
mutableState.approximateSize += dbRecord.ExecutionInfo.Size() - mutableState.executionInfo.Size()
mutableState.executionInfo = dbRecord.ExecutionInfo
mutableState.hBuilder = historybuilder.New(
mutableState.timeSource,
mutableState.shard.GenerateTaskIDs,
common.EmptyVersion,
dbRecord.NextEventId,
dbRecord.BufferedEvents,
mutableState.metricsHandler,
)
mutableState.currentVersion = common.EmptyVersion
mutableState.bufferEventsInDB = dbRecord.BufferedEvents
mutableState.stateInDB = dbRecord.ExecutionState.State
mutableState.nextEventIDInDB = dbRecord.NextEventId
mutableState.dbRecordVersion = dbRecordVersion
mutableState.checksum = dbRecord.Checksum
if len(dbRecord.Checksum.GetValue()) > 0 {
switch {
case mutableState.shouldInvalidateCheckum():
mutableState.checksum = nil
metrics.MutableStateChecksumInvalidated.With(mutableState.metricsHandler).Record(1)
case mutableState.shouldVerifyChecksum():
if err := verifyMutableStateChecksum(mutableState, dbRecord.Checksum); err != nil {
// we ignore checksum verification errors for now until this
// feature is tested and/or we have mechanisms in place to deal
// with these types of errors
metrics.MutableStateChecksumMismatch.With(mutableState.metricsHandler).Record(1)
mutableState.logError("mutable state checksum mismatch", tag.Error(err))
}
}
}
mutableState.mustInitHSM()
return mutableState, nil
}
func NewSanitizedMutableState(
shard shard.Context,
eventsCache events.Cache,
logger log.Logger,
namespaceEntry *namespace.Namespace,
mutableStateRecord *persistencespb.WorkflowMutableState,
lastFirstEventTxnID int64,
lastWriteVersion int64,
) (*MutableStateImpl, error) {
mutableState, err := NewMutableStateFromDB(shard, eventsCache, logger, namespaceEntry, mutableStateRecord, 1)
if err != nil {
return nil, err
}
// sanitize data
// Some values stored in mutable state are cluster or shard specific.
// E.g task status (if task is created or not), taskID (derived from shard rangeID), txnID (derived from shard rangeID), etc.
// Those fields should not be replicated across clusters and should be sanitized.
mutableState.executionInfo.WorkflowExecutionTimerTaskStatus = TimerTaskStatusNone
mutableState.executionInfo.LastFirstEventTxnId = lastFirstEventTxnID
mutableState.executionInfo.CloseVisibilityTaskId = common.EmptyVersion
mutableState.executionInfo.CloseTransferTaskId = common.EmptyVersion
// TODO: after adding cluster to clock info, no need to reset clock here
mutableState.executionInfo.ParentClock = nil
for _, childExecutionInfo := range mutableState.pendingChildExecutionInfoIDs {
childExecutionInfo.Clock = nil
}
mutableState.currentVersion = lastWriteVersion
return mutableState, nil
}
func NewMutableStateInChain(
shardContext shard.Context,
eventsCache events.Cache,
logger log.Logger,
namespaceEntry *namespace.Namespace,
workflowID string,
runID string,
startTime time.Time,
currentMutableState MutableState,
) MutableState {
newMutableState := NewMutableState(
shardContext,
eventsCache,
logger,
namespaceEntry,
workflowID,
runID,
startTime,
)
// carry over necessary fields from current mutable state
newMutableState.executionInfo.WorkflowExecutionTimerTaskStatus = currentMutableState.GetExecutionInfo().WorkflowExecutionTimerTaskStatus
// TODO: Today other information like autoResetPoints, previousRunID, firstRunID, etc.
// are carried over in AddWorkflowExecutionStartedEventWithOptions. Ideally all information
// should be carried over here since some information is not part of the startedEvent.
return newMutableState
}
func (ms *MutableStateImpl) mustInitHSM() {
// Error only occurs if some initialization path forgets to register the workflow state machine.
stateMachineNode, err := hsm.NewRoot(ms.shard.StateMachineRegistry(), StateMachineType.ID, ms, ms.executionInfo.SubStateMachinesByType, ms)
if err != nil {
panic(err)
}
ms.stateMachineNode = stateMachineNode
}
func (ms *MutableStateImpl) HSM() *hsm.Node {
return ms.stateMachineNode
}
// GetNexusCompletion converts a workflow completion event into a [nexus.OperationCompletion].
// Completions may be sent to arbitrary third parties, we intentionally do not include any termination reasons, and
// expose only failure messages.
func (ms *MutableStateImpl) GetNexusCompletion(ctx context.Context) (nexus.OperationCompletion, error) {
ce, err := ms.GetCompletionEvent(ctx)
if err != nil {
return nil, err
}
switch ce.GetEventType() {
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_COMPLETED:
payloads := ce.GetWorkflowExecutionCompletedEventAttributes().GetResult().GetPayloads()
var p *commonpb.Payload
if len(payloads) > 0 {
// All of our SDKs support returning a single value from workflows, we can safely ignore the
// rest of the payloads. Additionally, even if a workflow could return more than a single value,
// Nexus does not support it.
p = payloads[0]
} else {
p = &commonpb.Payload{}
}
completion, err := nexus.NewOperationCompletionSuccessful(p, nexus.OperationCompletionSuccesfulOptions{
Serializer: commonnexus.PayloadSerializer,
})
if err != nil {
return nil, serviceerror.NewInternal(fmt.Sprintf("failed to construct Nexus completion: %v", err))
}
return completion, nil
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_FAILED:
f := commonnexus.APIFailureToNexusFailure(ce.GetWorkflowExecutionFailedEventAttributes().GetFailure())
return &nexus.OperationCompletionUnsuccessful{
State: nexus.OperationStateFailed,
Failure: f,
}, nil
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_CANCELED:
return &nexus.OperationCompletionUnsuccessful{
State: nexus.OperationStateCanceled,
Failure: &nexus.Failure{Message: "operation canceled"},
}, nil
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_TERMINATED:
return &nexus.OperationCompletionUnsuccessful{
State: nexus.OperationStateFailed,
Failure: &nexus.Failure{Message: "operation terminated"},
}, nil
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_TIMED_OUT:
return &nexus.OperationCompletionUnsuccessful{
State: nexus.OperationStateFailed,
Failure: &nexus.Failure{Message: "operation exceeded internal timeout"},
}, nil
}
return nil, serviceerror.NewInternal(fmt.Sprintf("invalid workflow execution status: %v", ce.GetEventType()))
}
func (ms *MutableStateImpl) CloneToProto() *persistencespb.WorkflowMutableState {
msProto := &persistencespb.WorkflowMutableState{
ActivityInfos: ms.pendingActivityInfoIDs,
TimerInfos: ms.pendingTimerInfoIDs,
ChildExecutionInfos: ms.pendingChildExecutionInfoIDs,
RequestCancelInfos: ms.pendingRequestCancelInfoIDs,
SignalInfos: ms.pendingSignalInfoIDs,
SignalRequestedIds: convert.StringSetToSlice(ms.pendingSignalRequestedIDs),
ExecutionInfo: ms.executionInfo,
ExecutionState: ms.executionState,
NextEventId: ms.hBuilder.NextEventID(),
BufferedEvents: ms.bufferEventsInDB,
Checksum: ms.checksum,
}
return common.CloneProto(msProto)
}
func (ms *MutableStateImpl) GetWorkflowKey() definition.WorkflowKey {
return definition.NewWorkflowKey(
ms.executionInfo.NamespaceId,
ms.executionInfo.WorkflowId,
ms.executionState.RunId,
)
}
func (ms *MutableStateImpl) GetCurrentBranchToken() ([]byte, error) {
currentVersionHistory, err := versionhistory.GetCurrentVersionHistory(ms.executionInfo.VersionHistories)
if err != nil {
return nil, err
}
return currentVersionHistory.GetBranchToken(), nil
}
func (ms *MutableStateImpl) getCurrentBranchTokenAndEventVersion(eventID int64) ([]byte, int64, error) {
currentVersionHistory, err := versionhistory.GetCurrentVersionHistory(ms.executionInfo.VersionHistories)
if err != nil {
return nil, 0, err
}
version, err := versionhistory.GetVersionHistoryEventVersion(currentVersionHistory, eventID)
if err != nil {
return nil, 0, err
}
return currentVersionHistory.GetBranchToken(), version, nil
}
// SetHistoryTree set treeID/historyBranches
func (ms *MutableStateImpl) SetHistoryTree(
executionTimeout *durationpb.Duration,
runTimeout *durationpb.Duration,
treeID string,
) error {
// NOTE: Unfortunately execution timeout and run timeout are not yet initialized into ms.executionInfo at this point.
// TODO: Consider explicitly initializing mutable state with these timeout parameters instead of passing them in.
workflowKey := ms.GetWorkflowKey()
var retentionDuration *durationpb.Duration
if duration := ms.namespaceEntry.Retention(); duration > 0 {
retentionDuration = durationpb.New(duration)
}
initialBranchToken, err := ms.shard.GetExecutionManager().GetHistoryBranchUtil().NewHistoryBranch(
workflowKey.NamespaceID,
workflowKey.WorkflowID,
workflowKey.RunID,
treeID,
nil,
[]*persistencespb.HistoryBranchRange{},
runTimeout.AsDuration(),
executionTimeout.AsDuration(),
retentionDuration.AsDuration(),
)
if err != nil {
return err
}
return ms.SetCurrentBranchToken(initialBranchToken)
}
func (ms *MutableStateImpl) SetCurrentBranchToken(
branchToken []byte,
) error {
currentVersionHistory, err := versionhistory.GetCurrentVersionHistory(ms.executionInfo.VersionHistories)
if err != nil {
return err
}
versionhistory.SetVersionHistoryBranchToken(currentVersionHistory, branchToken)
return nil
}
func (ms *MutableStateImpl) SetHistoryBuilder(hBuilder *historybuilder.HistoryBuilder) {
ms.hBuilder = hBuilder
}
func (ms *MutableStateImpl) SetBaseWorkflow(
baseRunID string,
baseRunLowestCommonAncestorEventID int64,
baseRunLowestCommonAncestorEventVersion int64,
) {
ms.executionInfo.BaseExecutionInfo = &workflowspb.BaseExecutionInfo{
RunId: baseRunID,
LowestCommonAncestorEventId: baseRunLowestCommonAncestorEventID,
LowestCommonAncestorEventVersion: baseRunLowestCommonAncestorEventVersion,
}
}
func (ms *MutableStateImpl) GetBaseWorkflowInfo() *workflowspb.BaseExecutionInfo {
return ms.executionInfo.BaseExecutionInfo
}
func (ms *MutableStateImpl) GetExecutionInfo() *persistencespb.WorkflowExecutionInfo {
return ms.executionInfo
}
func (ms *MutableStateImpl) GetExecutionState() *persistencespb.WorkflowExecutionState {
return ms.executionState
}
func (ms *MutableStateImpl) FlushBufferedEvents() {
if ms.HasStartedWorkflowTask() {
return
}
ms.updatePendingEventIDs(ms.hBuilder.FlushBufferToCurrentBatch())
}
func (ms *MutableStateImpl) UpdateCurrentVersion(
version int64,
forceUpdate bool,
) error {
if state, _ := ms.GetWorkflowStateStatus(); state == enumsspb.WORKFLOW_EXECUTION_STATE_COMPLETED {
// always set current version to last write version when workflow is completed
lastWriteVersion, err := ms.GetLastWriteVersion()
if err != nil {
return err
}
ms.currentVersion = lastWriteVersion
return nil
}
versionHistory, err := versionhistory.GetCurrentVersionHistory(ms.executionInfo.VersionHistories)
if err != nil {
return err
}
if !versionhistory.IsEmptyVersionHistory(versionHistory) {
// this make sure current version >= last write version
versionHistoryItem, err := versionhistory.GetLastVersionHistoryItem(versionHistory)
if err != nil {
return err
}
ms.currentVersion = versionHistoryItem.GetVersion()
}
if version > ms.currentVersion || forceUpdate {
ms.currentVersion = version
}
ms.hBuilder = historybuilder.New(
ms.timeSource,
ms.shard.GenerateTaskIDs,
ms.currentVersion,
ms.nextEventIDInDB,
ms.bufferEventsInDB,
ms.metricsHandler,
)
return nil
}
func (ms *MutableStateImpl) GetCurrentVersion() int64 {
if ms.executionInfo.VersionHistories != nil {
return ms.currentVersion
}
return common.EmptyVersion
}
func (ms *MutableStateImpl) GetStartVersion() (int64, error) {
if ms.executionInfo.VersionHistories != nil {
versionHistory, err := versionhistory.GetCurrentVersionHistory(ms.executionInfo.VersionHistories)
if err != nil {
return 0, err
}
firstItem, err := versionhistory.GetFirstVersionHistoryItem(versionHistory)
if err != nil {
return 0, err
}
return firstItem.GetVersion(), nil
}
return common.EmptyVersion, nil
}
func (ms *MutableStateImpl) GetLastWriteVersion() (int64, error) {
if ms.executionInfo.VersionHistories != nil {
versionHistory, err := versionhistory.GetCurrentVersionHistory(ms.executionInfo.VersionHistories)
if err != nil {
return 0, err
}
lastItem, err := versionhistory.GetLastVersionHistoryItem(versionHistory)
if err != nil {
return 0, err
}
return lastItem.GetVersion(), nil
}
return common.EmptyVersion, nil
}
func (ms *MutableStateImpl) IsCurrentWorkflowGuaranteed() bool {
// stateInDB is used like a bloom filter:
//
// 1. stateInDB being created / running meaning that this workflow must be the current
// workflow (assuming there is no rebuild of mutable state).
// 2. stateInDB being completed does not guarantee this workflow being the current workflow
// 3. stateInDB being zombie guarantees this workflow not being the current workflow
// 4. stateInDB cannot be void, void is only possible when mutable state is just initialized
switch ms.stateInDB {
case enumsspb.WORKFLOW_EXECUTION_STATE_VOID:
return false
case enumsspb.WORKFLOW_EXECUTION_STATE_CREATED:
return true
case enumsspb.WORKFLOW_EXECUTION_STATE_RUNNING:
return true
case enumsspb.WORKFLOW_EXECUTION_STATE_COMPLETED:
return false
case enumsspb.WORKFLOW_EXECUTION_STATE_ZOMBIE:
return false
case enumsspb.WORKFLOW_EXECUTION_STATE_CORRUPTED:
return false
default:
panic(fmt.Sprintf("unknown workflow state: %v", ms.executionState.State))
}
}
func (ms *MutableStateImpl) GetNamespaceEntry() *namespace.Namespace {
return ms.namespaceEntry
}
// AddHistoryEvent adds any history event to this workflow execution.
// The provided setAttributes function should be used to set the attributes on the event.
func (ms *MutableStateImpl) AddHistoryEvent(t enumspb.EventType, setAttributes func(*history.HistoryEvent)) *history.HistoryEvent {
event := ms.hBuilder.AddHistoryEvent(t, setAttributes)
ms.writeEventToCache(event)
ms.currentTransactionAddedStateMachineEventTypes = append(ms.currentTransactionAddedStateMachineEventTypes, t)
return event
}
func (ms *MutableStateImpl) LoadHistoryEvent(ctx context.Context, token []byte) (*historypb.HistoryEvent, error) {
ref := &tokenspb.HistoryEventRef{}
err := proto.Unmarshal(token, ref)
if err != nil {
return nil, err
}
branchToken, version, err := ms.getCurrentBranchTokenAndEventVersion(ref.EventId)
if err != nil {
return nil, err
}
wfKey := ms.GetWorkflowKey()
eventKey := events.EventKey{
NamespaceID: namespace.ID(wfKey.NamespaceID),
WorkflowID: wfKey.WorkflowID,
RunID: wfKey.RunID,
EventID: ref.EventId,
Version: version,
}
return ms.eventsCache.GetEvent(ctx, ms.shard.GetShardID(), eventKey, ref.EventBatchId, branchToken)
}
func (ms *MutableStateImpl) CurrentTaskQueue() *taskqueuepb.TaskQueue {
if ms.IsStickyTaskQueueSet() {
return &taskqueuepb.TaskQueue{
Name: ms.executionInfo.StickyTaskQueue,
Kind: enumspb.TASK_QUEUE_KIND_STICKY,
NormalName: ms.executionInfo.TaskQueue,
}
}
return &taskqueuepb.TaskQueue{
Name: ms.executionInfo.TaskQueue,
Kind: enumspb.TASK_QUEUE_KIND_NORMAL,
}
}
func (ms *MutableStateImpl) SetStickyTaskQueue(name string, scheduleToStartTimeout *durationpb.Duration) {
ms.executionInfo.StickyTaskQueue = name
ms.executionInfo.StickyScheduleToStartTimeout = scheduleToStartTimeout
}
func (ms *MutableStateImpl) ClearStickyTaskQueue() {
ms.executionInfo.StickyTaskQueue = ""
ms.executionInfo.StickyScheduleToStartTimeout = nil
}
func (ms *MutableStateImpl) IsStickyTaskQueueSet() bool {
return ms.executionInfo.StickyTaskQueue != ""
}
// TaskQueueScheduleToStartTimeout returns TaskQueue struct and corresponding StartToClose timeout.
// Task queue kind (sticky or normal) is set based on comparison of normal task queue name
// in mutable state and provided name.
// ScheduleToStartTimeout is set based on queue kind and workflow task type.
func (ms *MutableStateImpl) TaskQueueScheduleToStartTimeout(tqName string) (*taskqueuepb.TaskQueue, *durationpb.Duration) {
isStickyTq := ms.executionInfo.StickyTaskQueue == tqName
if isStickyTq {
return &taskqueuepb.TaskQueue{
Name: ms.executionInfo.StickyTaskQueue,
Kind: enumspb.TASK_QUEUE_KIND_STICKY,
NormalName: ms.executionInfo.TaskQueue,
}, ms.executionInfo.StickyScheduleToStartTimeout
}
// If tqName is normal task queue name.
normalTq := &taskqueuepb.TaskQueue{
Name: ms.executionInfo.TaskQueue,
Kind: enumspb.TASK_QUEUE_KIND_NORMAL,
}
if ms.executionInfo.WorkflowTaskType == enumsspb.WORKFLOW_TASK_TYPE_SPECULATIVE {
// Speculative WFT has ScheduleToStartTimeout even on normal task queue.
// See comment in GenerateScheduleSpeculativeWorkflowTaskTasks for details.
return normalTq, durationpb.New(tasks.SpeculativeWorkflowTaskScheduleToStartTimeout)
}
// No WFT ScheduleToStart timeout for normal WFT on normal task queue.
return normalTq, ms.executionInfo.WorkflowRunTimeout
}
func (ms *MutableStateImpl) GetWorkflowType() *commonpb.WorkflowType {
wType := &commonpb.WorkflowType{}
wType.Name = ms.executionInfo.WorkflowTypeName
return wType
}
func (ms *MutableStateImpl) GetQueryRegistry() QueryRegistry {
return ms.QueryRegistry
}
// VisitUpdates visits mutable state update entries, ordered by the ID of the history event pointed to by the mutable
// state entry. Thus, for example, updates entries in Admitted state will be visited in the order that their Admitted
// events were added to history.
func (ms *MutableStateImpl) VisitUpdates(visitor func(updID string, updInfo *updatespb.UpdateInfo)) {
type updateEvent struct {
updId string
updInfo *updatespb.UpdateInfo
eventId int64
}
var updateEvents []updateEvent
for updID, updInfo := range ms.executionInfo.GetUpdateInfos() {
u := updateEvent{
updId: updID,
updInfo: updInfo,
}
if adm := updInfo.GetAdmission(); adm != nil {
u.eventId = adm.GetHistoryPointer().EventId
} else if acc := updInfo.GetAcceptance(); acc != nil {
u.eventId = acc.EventId
} else if com := updInfo.GetCompletion(); com != nil {
u.eventId = com.EventId
}
updateEvents = append(updateEvents, u)
}
slices.SortFunc(updateEvents, func(u1, u2 updateEvent) int { return cmp.Compare(u1.eventId, u2.eventId) })
for _, u := range updateEvents {
visitor(u.updId, u.updInfo)
}
}
func (ms *MutableStateImpl) GetUpdateOutcome(
ctx context.Context,
updateID string,
) (*updatepb.Outcome, error) {
if ms.executionInfo.UpdateInfos == nil {
return nil, serviceerror.NewNotFound("update not found")
}
rec, ok := ms.executionInfo.UpdateInfos[updateID]
if !ok {
return nil, serviceerror.NewNotFound("update not found")
}
completion := rec.GetCompletion()
if completion == nil {
return nil, serviceerror.NewInternal("update has not completed")
}
currentBranchToken, version, err := ms.getCurrentBranchTokenAndEventVersion(completion.EventId)
if err != nil {
return nil, err
}
eventKey := events.EventKey{
NamespaceID: namespace.ID(ms.executionInfo.NamespaceId),
WorkflowID: ms.executionInfo.WorkflowId,
RunID: ms.executionState.RunId,
EventID: completion.EventId,
Version: version,
}
event, err := ms.eventsCache.GetEvent(ctx, ms.shard.GetShardID(), eventKey, completion.EventBatchId, currentBranchToken)
if err != nil {
return nil, err
}
attrs := event.GetWorkflowExecutionUpdateCompletedEventAttributes()
if attrs == nil {
return nil, serviceerror.NewInternal("event pointer does not reference an update completed event")
}
return attrs.GetOutcome(), nil
}
func (ms *MutableStateImpl) GetActivityScheduledEvent(
ctx context.Context,
scheduledEventID int64,
) (*historypb.HistoryEvent, error) {
ai, ok := ms.pendingActivityInfoIDs[scheduledEventID]
if !ok {
return nil, ErrMissingActivityInfo
}
currentBranchToken, version, err := ms.getCurrentBranchTokenAndEventVersion(ai.ScheduledEventId)
if err != nil {
return nil, err
}
event, err := ms.eventsCache.GetEvent(
ctx,
ms.shard.GetShardID(),
events.EventKey{
NamespaceID: namespace.ID(ms.executionInfo.NamespaceId),
WorkflowID: ms.executionInfo.WorkflowId,
RunID: ms.executionState.RunId,
EventID: ai.ScheduledEventId,
Version: version,
},
ai.ScheduledEventBatchId,
currentBranchToken,
)
if err != nil {
if common.IsNotFoundError(err) {
// do not return the original error
// since original error of type NotFound
// can cause task processing side to fail silently
return nil, ErrMissingActivityScheduledEvent
}
return nil, err
}
return event, nil
}
// GetActivityInfo gives details about an activity that is currently in progress.
func (ms *MutableStateImpl) GetActivityInfo(
scheduledEventID int64,
) (*persistencespb.ActivityInfo, bool) {
ai, ok := ms.pendingActivityInfoIDs[scheduledEventID]
return ai, ok
}
// GetActivityInfoWithTimerHeartbeat gives details about an activity that is currently in progress.
func (ms *MutableStateImpl) GetActivityInfoWithTimerHeartbeat(
scheduledEventID int64,
) (*persistencespb.ActivityInfo, time.Time, bool) {
ai, ok := ms.pendingActivityInfoIDs[scheduledEventID]
if !ok {
return nil, time.Time{}, false
}
timerVis, ok := ms.pendingActivityTimerHeartbeats[scheduledEventID]
return ai, timerVis, ok
}
// GetActivityByActivityID gives details about an activity that is currently in progress.
func (ms *MutableStateImpl) GetActivityByActivityID(
activityID string,
) (*persistencespb.ActivityInfo, bool) {
eventID, ok := ms.pendingActivityIDToEventID[activityID]
if !ok {
return nil, false
}
return ms.GetActivityInfo(eventID)
}
// GetActivityType gets the ActivityType from ActivityInfo if set,
// or from the events history otherwise for backwards compatibility.
func (ms *MutableStateImpl) GetActivityType(
ctx context.Context,
ai *persistencespb.ActivityInfo,
) (*commonpb.ActivityType, error) {
if ai.GetActivityType() != nil {
return ai.GetActivityType(), nil
}
// For backwards compatibility in case ActivityType is not set in ActivityInfo.
scheduledEvent, err := ms.GetActivityScheduledEvent(ctx, ai.ScheduledEventId)
if err != nil {
return nil, err
}
return scheduledEvent.GetActivityTaskScheduledEventAttributes().ActivityType, nil
}
// GetChildExecutionInfo gives details about a child execution that is currently in progress.
func (ms *MutableStateImpl) GetChildExecutionInfo(
initiatedEventID int64,
) (*persistencespb.ChildExecutionInfo, bool) {
ci, ok := ms.pendingChildExecutionInfoIDs[initiatedEventID]
return ci, ok
}
// GetChildExecutionInitiatedEvent reads out the ChildExecutionInitiatedEvent from mutable state for in-progress child
// executions
func (ms *MutableStateImpl) GetChildExecutionInitiatedEvent(
ctx context.Context,
initiatedEventID int64,
) (*historypb.HistoryEvent, error) {
ci, ok := ms.pendingChildExecutionInfoIDs[initiatedEventID]
if !ok {
return nil, ErrMissingChildWorkflowInfo
}
currentBranchToken, version, err := ms.getCurrentBranchTokenAndEventVersion(ci.InitiatedEventId)
if err != nil {
return nil, err
}
event, err := ms.eventsCache.GetEvent(
ctx,
ms.shard.GetShardID(),
events.EventKey{
NamespaceID: namespace.ID(ms.executionInfo.NamespaceId),
WorkflowID: ms.executionInfo.WorkflowId,
RunID: ms.executionState.RunId,
EventID: ci.InitiatedEventId,
Version: version,
},
ci.InitiatedEventBatchId,
currentBranchToken,
)
if err != nil {
if common.IsNotFoundError(err) {
// do not return the original error
// since original error of type NotFound
// can cause task processing side to fail silently
return nil, ErrMissingChildWorkflowInitiatedEvent
}
return nil, err
}
return event, nil
}
// GetRequestCancelInfo gives details about a request cancellation that is currently in progress.
func (ms *MutableStateImpl) GetRequestCancelInfo(
initiatedEventID int64,
) (*persistencespb.RequestCancelInfo, bool) {
ri, ok := ms.pendingRequestCancelInfoIDs[initiatedEventID]
return ri, ok
}
func (ms *MutableStateImpl) GetRequesteCancelExternalInitiatedEvent(
ctx context.Context,
initiatedEventID int64,
) (*historypb.HistoryEvent, error) {
ri, ok := ms.pendingRequestCancelInfoIDs[initiatedEventID]
if !ok {
return nil, ErrMissingRequestCancelInfo
}
currentBranchToken, version, err := ms.getCurrentBranchTokenAndEventVersion(ri.InitiatedEventId)
if err != nil {
return nil, err
}
event, err := ms.eventsCache.GetEvent(
ctx,
ms.shard.GetShardID(),
events.EventKey{
NamespaceID: namespace.ID(ms.executionInfo.NamespaceId),
WorkflowID: ms.executionInfo.WorkflowId,
RunID: ms.executionState.RunId,
EventID: ri.InitiatedEventId,
Version: version,
},
ri.InitiatedEventBatchId,
currentBranchToken,
)
if err != nil {
if common.IsNotFoundError(err) {
// do not return the original error
// since original error of type NotFound
// can cause task processing side to fail silently
return nil, ErrMissingRequestCancelInfo
}
return nil, err
}
return event, nil
}
func (ms *MutableStateImpl) GetRetryBackoffDuration(
failure *failurepb.Failure,
) (time.Duration, enumspb.RetryState) {
info := ms.executionInfo
if !info.HasRetryPolicy {
return backoff.NoBackoff, enumspb.RETRY_STATE_RETRY_POLICY_NOT_SET
}
return getBackoffInterval(
ms.timeSource.Now(),
info.Attempt,
info.RetryMaximumAttempts,
info.RetryInitialInterval,
info.RetryMaximumInterval,
info.WorkflowExecutionExpirationTime,
info.RetryBackoffCoefficient,
failure,
info.RetryNonRetryableErrorTypes,
)
}
func (ms *MutableStateImpl) GetCronBackoffDuration() time.Duration {
if ms.executionInfo.CronSchedule == "" {
return backoff.NoBackoff
}
executionTime := timestamp.TimeValue(ms.GetExecutionInfo().GetExecutionTime())
return backoff.GetBackoffForNextSchedule(ms.executionInfo.CronSchedule, executionTime, ms.timeSource.Now())
}
// GetSignalInfo get the details about a signal request that is currently in progress.
func (ms *MutableStateImpl) GetSignalInfo(
initiatedEventID int64,
) (*persistencespb.SignalInfo, bool) {
ri, ok := ms.pendingSignalInfoIDs[initiatedEventID]
return ri, ok
}
// GetSignalExternalInitiatedEvent get the details about signal external workflow
func (ms *MutableStateImpl) GetSignalExternalInitiatedEvent(
ctx context.Context,
initiatedEventID int64,
) (*historypb.HistoryEvent, error) {
si, ok := ms.pendingSignalInfoIDs[initiatedEventID]
if !ok {
return nil, ErrMissingSignalInfo
}
currentBranchToken, version, err := ms.getCurrentBranchTokenAndEventVersion(si.InitiatedEventId)
if err != nil {
return nil, err
}
event, err := ms.eventsCache.GetEvent(
ctx,
ms.shard.GetShardID(),
events.EventKey{
NamespaceID: namespace.ID(ms.executionInfo.NamespaceId),
WorkflowID: ms.executionInfo.WorkflowId,
RunID: ms.executionState.RunId,
EventID: si.InitiatedEventId,
Version: version,
},
si.InitiatedEventBatchId,
currentBranchToken,
)
if err != nil {
if common.IsNotFoundError(err) {
// do not return the original error
// since original error of type NotFound
// can cause task processing side to fail silently
return nil, ErrMissingSignalInitiatedEvent
}
return nil, err
}
return event, nil
}
// GetCompletionEvent retrieves the workflow completion event from mutable state
func (ms *MutableStateImpl) GetCompletionEvent(
ctx context.Context,
) (*historypb.HistoryEvent, error) {
if ms.executionState.State != enumsspb.WORKFLOW_EXECUTION_STATE_COMPLETED {
return nil, ErrMissingWorkflowCompletionEvent
}
// Completion EventID is always one less than NextEventID after workflow is completed
completionEventID := ms.hBuilder.NextEventID() - 1
firstEventID := ms.executionInfo.CompletionEventBatchId
currentBranchToken, version, err := ms.getCurrentBranchTokenAndEventVersion(completionEventID)
if err != nil {
return nil, err
}
event, err := ms.eventsCache.GetEvent(
ctx,
ms.shard.GetShardID(),
events.EventKey{
NamespaceID: namespace.ID(ms.executionInfo.NamespaceId),
WorkflowID: ms.executionInfo.WorkflowId,
RunID: ms.executionState.RunId,
EventID: completionEventID,
Version: version,
},
firstEventID,
currentBranchToken,
)
if err != nil {
if common.IsNotFoundError(err) {
// do not return the original error
// since original error of type NotFound
// can cause task processing side to fail silently
return nil, ErrMissingWorkflowCompletionEvent
}
return nil, err
}
return event, nil
}
// GetWorkflowCloseTime returns workflow closed time, returns a zero time for open workflow
func (ms *MutableStateImpl) GetWorkflowCloseTime(ctx context.Context) (time.Time, error) {
if ms.executionState.GetState() == enumsspb.WORKFLOW_EXECUTION_STATE_COMPLETED && ms.executionInfo.CloseTime == nil {
// This is for backward compatible. Prior to v1.16 does not have close time in mutable state (Added by 05/21/2022).
// TODO: remove this logic when all mutable state contains close time.
completionEvent, err := ms.GetCompletionEvent(ctx)
if err != nil {
return time.Time{}, err
}
return completionEvent.GetEventTime().AsTime(), nil
}
return ms.executionInfo.CloseTime.AsTime(), nil
}
// GetWorkflowExecutionDuration returns the workflow execution duration.
// Returns zero for open workflow.
func (ms *MutableStateImpl) GetWorkflowExecutionDuration(ctx context.Context) (time.Duration, error) {
closeTime, err := ms.GetWorkflowCloseTime(ctx)
if err != nil {
return 0, err
}
if closeTime.IsZero() || ms.executionInfo.ExecutionTime == nil {
return 0, nil
}
return closeTime.Sub(ms.executionInfo.ExecutionTime.AsTime()), nil
}
// GetStartEvent retrieves the workflow start event from mutable state
func (ms *MutableStateImpl) GetStartEvent(
ctx context.Context,
) (*historypb.HistoryEvent, error) {
currentBranchToken, err := ms.GetCurrentBranchToken()
if err != nil {
return nil, err
}
startVersion, err := ms.GetStartVersion()
if err != nil {
return nil, err
}
event, err := ms.eventsCache.GetEvent(
ctx,
ms.shard.GetShardID(),
events.EventKey{
NamespaceID: namespace.ID(ms.executionInfo.NamespaceId),
WorkflowID: ms.executionInfo.WorkflowId,
RunID: ms.executionState.RunId,
EventID: common.FirstEventID,
Version: startVersion,
},
common.FirstEventID,
currentBranchToken,
)
if err != nil {
if common.IsNotFoundError(err) {
// do not return the original error
// since original error of type NotFound
// can cause task processing side to fail silently
return nil, ErrMissingWorkflowStartEvent
}
return nil, err
}
return event, nil
}
func (ms *MutableStateImpl) GetFirstRunID(
ctx context.Context,
) (string, error) {
firstRunID := ms.executionInfo.FirstExecutionRunId
// This is needed for backwards compatibility. Workflow execution create with Temporal release v0.28.0 or earlier
// does not have FirstExecutionRunID stored as part of mutable state. If this is not set then load it from
// workflow execution started event.
if len(firstRunID) != 0 {
return firstRunID, nil
}
currentStartEvent, err := ms.GetStartEvent(ctx)
if err != nil {
return "", err
}
return currentStartEvent.GetWorkflowExecutionStartedEventAttributes().GetFirstExecutionRunId(), nil
}
// DeletePendingChildExecution deletes details about a ChildExecutionInfo.
func (ms *MutableStateImpl) DeletePendingChildExecution(
initiatedEventID int64,
) error {
if prev, ok := ms.pendingChildExecutionInfoIDs[initiatedEventID]; ok {
ms.approximateSize -= prev.Size() + int64SizeBytes
delete(ms.pendingChildExecutionInfoIDs, initiatedEventID)
} else {
ms.logError(
fmt.Sprintf("unable to find child workflow event ID: %v in mutable state", initiatedEventID),
tag.ErrorTypeInvalidMutableStateAction,
)
// log data inconsistency instead of returning an error
ms.logDataInconsistency()
}
delete(ms.updateChildExecutionInfos, initiatedEventID)
ms.deleteChildExecutionInfos[initiatedEventID] = struct{}{}
return nil
}
// DeletePendingRequestCancel deletes details about a RequestCancelInfo.
func (ms *MutableStateImpl) DeletePendingRequestCancel(
initiatedEventID int64,
) error {
if prev, ok := ms.pendingRequestCancelInfoIDs[initiatedEventID]; ok {
ms.approximateSize -= prev.Size() + int64SizeBytes
delete(ms.pendingRequestCancelInfoIDs, initiatedEventID)
} else {
ms.logError(
fmt.Sprintf("unable to find request cancel external workflow event ID: %v in mutable state", initiatedEventID),
tag.ErrorTypeInvalidMutableStateAction,
)
// log data inconsistency instead of returning an error
ms.logDataInconsistency()
}
delete(ms.updateRequestCancelInfos, initiatedEventID)
ms.deleteRequestCancelInfos[initiatedEventID] = struct{}{}
return nil
}
// DeletePendingSignal deletes details about a SignalInfo
func (ms *MutableStateImpl) DeletePendingSignal(
initiatedEventID int64,
) error {
if prev, ok := ms.pendingSignalInfoIDs[initiatedEventID]; ok {
ms.approximateSize -= prev.Size() + int64SizeBytes
delete(ms.pendingSignalInfoIDs, initiatedEventID)
} else {
ms.logError(
fmt.Sprintf("unable to find signal external workflow event ID: %v in mutable state", initiatedEventID),
tag.ErrorTypeInvalidMutableStateAction,
)
// log data inconsistency instead of returning an error
ms.logDataInconsistency()
}
delete(ms.updateSignalInfos, initiatedEventID)
ms.deleteSignalInfos[initiatedEventID] = struct{}{}
return nil
}
func (ms *MutableStateImpl) writeEventToCache(
event *historypb.HistoryEvent,
) {
// For start event: store it here so the recordWorkflowStarted transfer task doesn't need to
// load it from database.
// For completion event: store it here so we can communicate the result to parent execution
// during the processing of DeleteTransferTask without loading this event from database.
// For Update events: store it here so that Update disposition lookups can be fast.
ms.eventsCache.PutEvent(
events.EventKey{
NamespaceID: namespace.ID(ms.executionInfo.NamespaceId),
WorkflowID: ms.executionInfo.WorkflowId,
RunID: ms.executionState.RunId,
EventID: event.GetEventId(),
Version: event.GetVersion(),
},
event,
)
}
func (ms *MutableStateImpl) HasParentExecution() bool {
return ms.executionInfo.ParentNamespaceId != "" && ms.executionInfo.ParentWorkflowId != ""
}
func (ms *MutableStateImpl) UpdateActivityProgress(
ai *persistencespb.ActivityInfo,
request *workflowservice.RecordActivityTaskHeartbeatRequest,
) {
if prev, existed := ms.pendingActivityInfoIDs[ai.ScheduledEventId]; existed {
ms.approximateSize -= prev.Size()
}
ai.Version = ms.GetCurrentVersion()
ai.LastHeartbeatDetails = request.Details
now := ms.timeSource.Now()
ai.LastHeartbeatUpdateTime = timestamppb.New(now)
ms.updateActivityInfos[ai.ScheduledEventId] = ai
ms.approximateSize += ai.Size()
ms.syncActivityTasks[ai.ScheduledEventId] = struct{}{}
}
// UpdateActivityInfo applies the necessary activity information
func (ms *MutableStateImpl) UpdateActivityInfo(
incomingActivityInfo *historyservice.ActivitySyncInfo,
resetActivityTimerTaskStatus bool,
) error {
ai, ok := ms.pendingActivityInfoIDs[incomingActivityInfo.GetScheduledEventId()]
if !ok {
ms.logError(
fmt.Sprintf("unable to find activity event ID: %v in mutable state", incomingActivityInfo.GetScheduledEventId()),
tag.ErrorTypeInvalidMutableStateAction,
)
return ErrMissingActivityInfo
}
ms.approximateSize -= ai.Size()
ai.Version = incomingActivityInfo.GetVersion()
ai.ScheduledTime = incomingActivityInfo.GetScheduledTime()
ai.StartedEventId = incomingActivityInfo.GetStartedEventId()
ai.LastHeartbeatUpdateTime = incomingActivityInfo.GetLastHeartbeatTime()
if ai.StartedEventId == common.EmptyEventID {
ai.StartedTime = nil
} else {
ai.StartedTime = incomingActivityInfo.GetStartedTime()
}
ai.LastHeartbeatDetails = incomingActivityInfo.GetDetails()
ai.Attempt = incomingActivityInfo.GetAttempt()
ai.RetryLastWorkerIdentity = incomingActivityInfo.GetLastWorkerIdentity()
ai.RetryLastFailure = incomingActivityInfo.LastFailure
if resetActivityTimerTaskStatus {
ai.TimerTaskStatus = TimerTaskStatusNone
}
ms.updateActivityInfos[ai.ScheduledEventId] = ai
ms.approximateSize += ai.Size()
return nil
}
// UpdateActivity updates an activity
func (ms *MutableStateImpl) UpdateActivity(
ai *persistencespb.ActivityInfo,
) error {
prev, ok := ms.pendingActivityInfoIDs[ai.ScheduledEventId]
if !ok {
ms.logError(
fmt.Sprintf("unable to find activity ID: %v in mutable state", ai.ActivityId),
tag.ErrorTypeInvalidMutableStateAction,
)
return ErrMissingActivityInfo
}
ms.pendingActivityInfoIDs[ai.ScheduledEventId] = ai
ms.updateActivityInfos[ai.ScheduledEventId] = ai
ms.approximateSize += ai.Size() - prev.Size()
return nil
}
// UpdateActivityWithTimerHeartbeat updates an activity
func (ms *MutableStateImpl) UpdateActivityWithTimerHeartbeat(
ai *persistencespb.ActivityInfo,
timerTimeoutVisibility time.Time,
) error {
err := ms.UpdateActivity(ai)
if err != nil {
return err
}
ms.pendingActivityTimerHeartbeats[ai.ScheduledEventId] = timerTimeoutVisibility
return nil
}
// DeleteActivity deletes details about an activity.
func (ms *MutableStateImpl) DeleteActivity(
scheduledEventID int64,
) error {
if activityInfo, ok := ms.pendingActivityInfoIDs[scheduledEventID]; ok {
delete(ms.pendingActivityInfoIDs, scheduledEventID)
delete(ms.pendingActivityTimerHeartbeats, scheduledEventID)
ms.approximateSize -= activityInfo.Size() + int64SizeBytes
if _, ok = ms.pendingActivityIDToEventID[activityInfo.ActivityId]; ok {
delete(ms.pendingActivityIDToEventID, activityInfo.ActivityId)
} else {
ms.logError(
fmt.Sprintf("unable to find activity ID: %v in mutable state", activityInfo.ActivityId),
tag.ErrorTypeInvalidMutableStateAction,
)
// log data inconsistency instead of returning an error
ms.logDataInconsistency()
}
} else {
ms.logError(
fmt.Sprintf("unable to find activity event id: %v in mutable state", scheduledEventID),
tag.ErrorTypeInvalidMutableStateAction,
)
// log data inconsistency instead of returning an error
ms.logDataInconsistency()
}
delete(ms.updateActivityInfos, scheduledEventID)
delete(ms.syncActivityTasks, scheduledEventID)
ms.deleteActivityInfos[scheduledEventID] = struct{}{}
return nil
}
// GetUserTimerInfo gives details about a user timer.
func (ms *MutableStateImpl) GetUserTimerInfo(
timerID string,
) (*persistencespb.TimerInfo, bool) {
timerInfo, ok := ms.pendingTimerInfoIDs[timerID]
return timerInfo, ok
}
// GetUserTimerInfoByEventID gives details about a user timer.
func (ms *MutableStateImpl) GetUserTimerInfoByEventID(
startEventID int64,
) (*persistencespb.TimerInfo, bool) {
timerID, ok := ms.pendingTimerEventIDToID[startEventID]
if !ok {
return nil, false
}
return ms.GetUserTimerInfo(timerID)
}
// UpdateUserTimer updates the user timer in progress.
func (ms *MutableStateImpl) UpdateUserTimer(
ti *persistencespb.TimerInfo,
) error {
timerID, ok := ms.pendingTimerEventIDToID[ti.GetStartedEventId()]
if !ok {
ms.logError(
fmt.Sprintf("unable to find timer event ID: %v in mutable state", ti.GetStartedEventId()),
tag.ErrorTypeInvalidMutableStateAction,
)
return ErrMissingTimerInfo
}
if _, ok := ms.pendingTimerInfoIDs[timerID]; !ok {
ms.logError(
fmt.Sprintf("unable to find timer ID: %v in mutable state", timerID),
tag.ErrorTypeInvalidMutableStateAction,
)
return ErrMissingTimerInfo
}
ms.pendingTimerInfoIDs[ti.TimerId] = ti
ms.updateTimerInfos[ti.TimerId] = ti
return nil
}
// DeleteUserTimer deletes an user timer.
func (ms *MutableStateImpl) DeleteUserTimer(
timerID string,
) error {
if timerInfo, ok := ms.pendingTimerInfoIDs[timerID]; ok {
delete(ms.pendingTimerInfoIDs, timerID)
ms.approximateSize -= timerInfo.Size() + len(timerID)
if _, ok = ms.pendingTimerEventIDToID[timerInfo.GetStartedEventId()]; ok {
delete(ms.pendingTimerEventIDToID, timerInfo.GetStartedEventId())
} else {
ms.logError(
fmt.Sprintf("unable to find timer event ID: %v in mutable state", timerID),
tag.ErrorTypeInvalidMutableStateAction,
)
// log data inconsistency instead of returning an error
ms.logDataInconsistency()
}
} else {
ms.logError(
fmt.Sprintf("unable to find timer ID: %v in mutable state", timerID),
tag.ErrorTypeInvalidMutableStateAction,
)
// log data inconsistency instead of returning an error
ms.logDataInconsistency()
}
delete(ms.updateTimerInfos, timerID)
ms.deleteTimerInfos[timerID] = struct{}{}
return nil
}
// GetWorkflowTaskByID returns details about the current workflow task by scheduled event ID.
func (ms *MutableStateImpl) GetWorkflowTaskByID(scheduledEventID int64) *WorkflowTaskInfo {
return ms.workflowTaskManager.GetWorkflowTaskByID(scheduledEventID)
}
func (ms *MutableStateImpl) GetPendingActivityInfos() map[int64]*persistencespb.ActivityInfo {
return ms.pendingActivityInfoIDs
}
func (ms *MutableStateImpl) GetPendingTimerInfos() map[string]*persistencespb.TimerInfo {
return ms.pendingTimerInfoIDs
}
func (ms *MutableStateImpl) GetPendingChildExecutionInfos() map[int64]*persistencespb.ChildExecutionInfo {
return ms.pendingChildExecutionInfoIDs
}
func (ms *MutableStateImpl) GetPendingRequestCancelExternalInfos() map[int64]*persistencespb.RequestCancelInfo {
return ms.pendingRequestCancelInfoIDs
}
func (ms *MutableStateImpl) GetPendingSignalExternalInfos() map[int64]*persistencespb.SignalInfo {
return ms.pendingSignalInfoIDs
}
func (ms *MutableStateImpl) HadOrHasWorkflowTask() bool {
return ms.workflowTaskManager.HadOrHasWorkflowTask()
}
func (ms *MutableStateImpl) HasPendingWorkflowTask() bool {
return ms.workflowTaskManager.HasPendingWorkflowTask()
}
func (ms *MutableStateImpl) GetPendingWorkflowTask() *WorkflowTaskInfo {
return ms.workflowTaskManager.GetPendingWorkflowTask()
}
func (ms *MutableStateImpl) HasStartedWorkflowTask() bool {
return ms.workflowTaskManager.HasStartedWorkflowTask()
}
func (ms *MutableStateImpl) GetStartedWorkflowTask() *WorkflowTaskInfo {
return ms.workflowTaskManager.GetStartedWorkflowTask()
}
func (ms *MutableStateImpl) IsTransientWorkflowTask() bool {
return ms.executionInfo.WorkflowTaskAttempt > 1
}
func (ms *MutableStateImpl) ClearTransientWorkflowTask() error {
if !ms.HasStartedWorkflowTask() {
return serviceerror.NewInternal("cannot clear transient workflow task when task is missing")
}
if !ms.IsTransientWorkflowTask() {
return serviceerror.NewInternal("cannot clear transient workflow task when task is not transient")
}
// this is transient workflow task
if ms.HasBufferedEvents() {
return serviceerror.NewInternal("cannot clear transient workflow task when there are buffered events")
}
// no buffered event
emptyWorkflowTaskInfo := &WorkflowTaskInfo{
Version: common.EmptyVersion,
ScheduledEventID: common.EmptyEventID,
StartedEventID: common.EmptyEventID,
RequestID: emptyUUID,
WorkflowTaskTimeout: time.Duration(0),
Attempt: 1,
StartedTime: timeZeroUTC,
ScheduledTime: timeZeroUTC,
TaskQueue: nil,
OriginalScheduledTime: timeZeroUTC,
Type: enumsspb.WORKFLOW_TASK_TYPE_UNSPECIFIED,
SuggestContinueAsNew: false,
HistorySizeBytes: 0,
}
ms.workflowTaskManager.UpdateWorkflowTask(emptyWorkflowTaskInfo)
return nil
}
func (ms *MutableStateImpl) GetAssignedBuildId() string {
return ms.executionInfo.AssignedBuildId
}
func (ms *MutableStateImpl) GetInheritedBuildId() string {
return ms.executionInfo.InheritedBuildId
}
func (ms *MutableStateImpl) GetMostRecentWorkerVersionStamp() *commonpb.WorkerVersionStamp {
return ms.executionInfo.MostRecentWorkerVersionStamp
}
func (ms *MutableStateImpl) HasBufferedEvents() bool {
return ms.hBuilder.HasBufferEvents()
}
// HasAnyBufferedEvent returns true if there is at least one buffered event that matches the provided filter.
func (ms *MutableStateImpl) HasAnyBufferedEvent(filter historybuilder.BufferedEventFilter) bool {
return ms.hBuilder.HasAnyBufferedEvent(filter)
}
// GetLastFirstEventIDTxnID returns last first event ID and corresponding transaction ID
// first event ID is the ID of a batch of events in a single history events record
func (ms *MutableStateImpl) GetLastFirstEventIDTxnID() (int64, int64) {
return ms.executionInfo.LastFirstEventId, ms.executionInfo.LastFirstEventTxnId
}
// GetNextEventID returns next event ID
func (ms *MutableStateImpl) GetNextEventID() int64 {
return ms.hBuilder.NextEventID()
}
// GetLastWorkflowTaskStartedEventID returns last started workflow task event ID
func (ms *MutableStateImpl) GetLastWorkflowTaskStartedEventID() int64 {
return ms.executionInfo.LastWorkflowTaskStartedEventId
}
func (ms *MutableStateImpl) IsWorkflowExecutionRunning() bool {
switch ms.executionState.State {
case enumsspb.WORKFLOW_EXECUTION_STATE_CREATED:
return true
case enumsspb.WORKFLOW_EXECUTION_STATE_RUNNING:
return true
case enumsspb.WORKFLOW_EXECUTION_STATE_COMPLETED:
return false
case enumsspb.WORKFLOW_EXECUTION_STATE_ZOMBIE:
return false
case enumsspb.WORKFLOW_EXECUTION_STATE_CORRUPTED:
return false
default:
panic(fmt.Sprintf("unknown workflow state: %v", ms.executionState.State))
}
}
func (ms *MutableStateImpl) IsCancelRequested() bool {
return ms.executionInfo.CancelRequested
}
func (ms *MutableStateImpl) IsWorkflowCloseAttempted() bool {
return ms.workflowCloseAttempted
}
func (ms *MutableStateImpl) IsSignalRequested(
requestID string,
) bool {
if _, ok := ms.pendingSignalRequestedIDs[requestID]; ok {
return true
}
return false
}
func (ms *MutableStateImpl) IsWorkflowPendingOnWorkflowTaskBackoff() bool {
workflowTaskBackoff := timestamp.TimeValue(ms.executionInfo.GetExecutionTime()).After(timestamp.TimeValue(ms.executionInfo.GetStartTime()))
if workflowTaskBackoff && !ms.HadOrHasWorkflowTask() {
return true
}
return false
}
// GetApproximatePersistedSize returns approximate size of in-memory objects that will be written to
// persistence + size of buffered events in history builder if they will not be flushed
func (ms *MutableStateImpl) GetApproximatePersistedSize() int {
// include buffered events in the size if they will not be flushed
if ms.BufferSizeAcceptable() && ms.HasStartedWorkflowTask() {
return ms.approximateSize + ms.hBuilder.SizeInBytesOfBufferedEvents()
}
return ms.approximateSize
}
func (ms *MutableStateImpl) AddSignalRequested(
requestID string,
) {
if ms.pendingSignalRequestedIDs == nil {
ms.pendingSignalRequestedIDs = make(map[string]struct{})
}
if ms.updateSignalRequestedIDs == nil {
ms.updateSignalRequestedIDs = make(map[string]struct{})
}
ms.pendingSignalRequestedIDs[requestID] = struct{}{}
ms.updateSignalRequestedIDs[requestID] = struct{}{}
ms.approximateSize += len(requestID)
}
func (ms *MutableStateImpl) DeleteSignalRequested(
requestID string,
) {
delete(ms.pendingSignalRequestedIDs, requestID)
delete(ms.updateSignalRequestedIDs, requestID)
ms.deleteSignalRequestedIDs[requestID] = struct{}{}
ms.approximateSize -= len(requestID)
}
func (ms *MutableStateImpl) addWorkflowExecutionStartedEventForContinueAsNew(
parentExecutionInfo *workflowspb.ParentExecutionInfo,
execution *commonpb.WorkflowExecution,
previousExecutionState MutableState,
command *commandpb.ContinueAsNewWorkflowExecutionCommandAttributes,
firstRunID string,
rootExecutionInfo *workflowspb.RootExecutionInfo,
) (*historypb.HistoryEvent, error) {
previousExecutionInfo := previousExecutionState.GetExecutionInfo()
taskQueue := previousExecutionInfo.TaskQueue
if command.TaskQueue != nil {
taskQueue = command.TaskQueue.GetName()
}
tq := &taskqueuepb.TaskQueue{
Name: taskQueue,
Kind: enumspb.TASK_QUEUE_KIND_NORMAL,
}
workflowType := previousExecutionInfo.WorkflowTypeName
if command.WorkflowType != nil {
workflowType = command.WorkflowType.GetName()
}
wType := &commonpb.WorkflowType{}
wType.Name = workflowType
var taskTimeout *durationpb.Duration
if command.GetWorkflowTaskTimeout().AsDuration() == 0 {
taskTimeout = previousExecutionInfo.DefaultWorkflowTaskTimeout
} else {
taskTimeout = command.GetWorkflowTaskTimeout()
}
// Workflow runTimeout is already set to the correct value in
// validateContinueAsNewWorkflowExecutionAttributes
runTimeout := command.GetWorkflowRunTimeout()
createRequest := &workflowservice.StartWorkflowExecutionRequest{
RequestId: uuid.New(),
Namespace: ms.namespaceEntry.Name().String(),
WorkflowId: execution.WorkflowId,
TaskQueue: tq,
WorkflowType: wType,
WorkflowExecutionTimeout: previousExecutionInfo.WorkflowExecutionTimeout,
WorkflowRunTimeout: runTimeout,
WorkflowTaskTimeout: taskTimeout,
Input: command.Input,
Header: command.Header,
RetryPolicy: command.RetryPolicy,
CronSchedule: command.CronSchedule,
Memo: command.Memo,
SearchAttributes: command.SearchAttributes,
// No need to request eager execution here (for now)
RequestEagerExecution: false,
}
enums.SetDefaultContinueAsNewInitiator(&command.Initiator)
var sourceVersionStamp *commonpb.WorkerVersionStamp
var inheritedBuildId string
if command.InheritBuildId {
inheritedBuildId = previousExecutionInfo.AssignedBuildId
if inheritedBuildId == "" {
// TODO: this is only needed for old versioning. get rid of StartWorkflowExecutionRequest.SourceVersionStamp
// [cleanup-old-wv]
// Copy version stamp to new workflow only if:
// - command says to use compatible version
// - using versioning
sourceVersionStamp = worker_versioning.StampIfUsingVersioning(previousExecutionInfo.MostRecentWorkerVersionStamp)
}
}
req := &historyservice.StartWorkflowExecutionRequest{
NamespaceId: ms.namespaceEntry.ID().String(),
StartRequest: createRequest,
ParentExecutionInfo: parentExecutionInfo,
LastCompletionResult: command.LastCompletionResult,
ContinuedFailure: command.GetFailure(),
ContinueAsNewInitiator: command.Initiator,
// enforce minimal interval between runs to prevent tight loop continue as new spin.
FirstWorkflowTaskBackoff: previousExecutionState.ContinueAsNewMinBackoff(command.BackoffStartInterval),
SourceVersionStamp: sourceVersionStamp,
RootExecutionInfo: rootExecutionInfo,
InheritedBuildId: inheritedBuildId,
}
if command.GetInitiator() == enumspb.CONTINUE_AS_NEW_INITIATOR_RETRY {
req.Attempt = previousExecutionState.GetExecutionInfo().Attempt + 1
} else {
req.Attempt = 1
}
workflowTimeoutTime := timestamp.TimeValue(previousExecutionState.GetExecutionInfo().WorkflowExecutionExpirationTime)
if !workflowTimeoutTime.IsZero() {
req.WorkflowExecutionExpirationTime = timestamppb.New(workflowTimeoutTime)
}
event, err := ms.AddWorkflowExecutionStartedEventWithOptions(
execution,
req,
previousExecutionInfo.AutoResetPoints,
previousExecutionState.GetExecutionState().GetRunId(),
firstRunID,
)
if err != nil {
return nil, err
}
var parentClock *clockspb.VectorClock
if parentExecutionInfo != nil {
parentClock = parentExecutionInfo.Clock
}
if _, err = ms.AddFirstWorkflowTaskScheduled(parentClock, event, false); err != nil {
return nil, err
}
return event, nil
}
func (ms *MutableStateImpl) ContinueAsNewMinBackoff(backoffDuration *durationpb.Duration) *durationpb.Duration {
// lifetime of previous execution
lifetime := ms.timeSource.Now().Sub(ms.executionInfo.StartTime.AsTime().UTC())
if ms.executionInfo.ExecutionTime != nil {
lifetime = ms.timeSource.Now().Sub(ms.executionInfo.ExecutionTime.AsTime().UTC())
}
interval := lifetime
if backoffDuration != nil {
// already has a backoff, add it to interval
interval += backoffDuration.AsDuration()
}
// minimal interval for continue as new to prevent tight continue as new loop
minInterval := ms.config.ContinueAsNewMinInterval(ms.namespaceEntry.Name().String())
if interval < minInterval {
// enforce a minimal backoff
return durationpb.New(minInterval - lifetime)
}
return backoffDuration
}
func (ms *MutableStateImpl) AddWorkflowExecutionStartedEvent(
execution *commonpb.WorkflowExecution,
startRequest *historyservice.StartWorkflowExecutionRequest,
) (*historypb.HistoryEvent, error) {
return ms.AddWorkflowExecutionStartedEventWithOptions(
execution,
startRequest,
nil, // resetPoints
"", // prevRunID
execution.GetRunId(),
)
}
func (ms *MutableStateImpl) AddWorkflowExecutionStartedEventWithOptions(
execution *commonpb.WorkflowExecution,
startRequest *historyservice.StartWorkflowExecutionRequest,
resetPoints *workflowpb.ResetPoints,
prevRunID string,
firstRunID string,
) (*historypb.HistoryEvent, error) {
opTag := tag.WorkflowActionWorkflowStarted
if err := ms.checkMutability(opTag); err != nil {
return nil, err
}
eventID := ms.GetNextEventID()
if eventID != common.FirstEventID {
ms.logger.Warn(mutableStateInvalidHistoryActionMsg, opTag,
tag.WorkflowEventID(eventID),
tag.ErrorTypeInvalidHistoryAction)
return nil, ms.createInternalServerError(opTag)
}
event := ms.hBuilder.AddWorkflowExecutionStartedEvent(
ms.executionInfo.StartTime.AsTime(),
startRequest,
resetPoints,
prevRunID,
firstRunID,
execution.GetRunId(),
)
if err := ms.ApplyWorkflowExecutionStartedEvent(
startRequest.GetParentExecutionInfo().GetClock(),
execution,
startRequest.StartRequest.GetRequestId(),
event,
); err != nil {
return nil, err
}
// TODO merge active & passive task generation
var err error
ms.executionInfo.WorkflowExecutionTimerTaskStatus, err = ms.taskGenerator.GenerateWorkflowStartTasks(
event,
)
if err != nil {
return nil, err
}
if err := ms.taskGenerator.GenerateRecordWorkflowStartedTasks(
event,
); err != nil {
return nil, err
}
return event, nil
}
func (ms *MutableStateImpl) ApplyWorkflowExecutionStartedEvent(
parentClock *clockspb.VectorClock,
execution *commonpb.WorkflowExecution,
requestID string,
startEvent *historypb.HistoryEvent,
) error {
if ms.executionInfo.NamespaceId != ms.namespaceEntry.ID().String() {
return serviceerror.NewInternal(fmt.Sprintf("applying conflicting namespace ID: %v != %v",
ms.executionInfo.NamespaceId, ms.namespaceEntry.ID().String()))
}
if ms.executionInfo.WorkflowId != execution.GetWorkflowId() {
return serviceerror.NewInternal(fmt.Sprintf("applying conflicting workflow ID: %v != %v",
ms.executionInfo.WorkflowId, execution.GetWorkflowId()))
}
if ms.executionState.RunId != execution.GetRunId() {
return serviceerror.NewInternal(fmt.Sprintf("applying conflicting run ID: %v != %v",
ms.executionState.RunId, execution.GetRunId()))
}
ms.approximateSize -= ms.executionInfo.Size()
event := startEvent.GetWorkflowExecutionStartedEventAttributes()
ms.executionState.CreateRequestId = requestID
ms.executionInfo.FirstExecutionRunId = event.GetFirstExecutionRunId()
ms.executionInfo.TaskQueue = event.TaskQueue.GetName()
ms.executionInfo.WorkflowTypeName = event.WorkflowType.GetName()
ms.executionInfo.WorkflowRunTimeout = event.GetWorkflowRunTimeout()
ms.executionInfo.WorkflowExecutionTimeout = event.GetWorkflowExecutionTimeout()
ms.executionInfo.DefaultWorkflowTaskTimeout = event.GetWorkflowTaskTimeout()
coll := callbacks.MachineCollection(ms.HSM())
for idx, cb := range event.GetCompletionCallbacks() {
machine := callbacks.NewCallback(startEvent.EventTime, callbacks.NewWorkflowClosedTrigger(), cb)
// Use the start event version and ID as part of the callback ID to ensure that callbacks have unique
// IDs that are deterministically created across clusters.
// TODO: Replicate the state machine state and allocate a uuid there instead of relying on history event
// replication.
id := fmt.Sprintf("%d-%d-%d", startEvent.GetVersion(), startEvent.GetEventId(), idx)
if _, err := coll.Add(id, machine); err != nil {
return err
}
}
if err := ms.UpdateWorkflowStateStatus(
enumsspb.WORKFLOW_EXECUTION_STATE_CREATED,
enumspb.WORKFLOW_EXECUTION_STATUS_RUNNING,
); err != nil {
return err
}
ms.executionInfo.LastWorkflowTaskStartedEventId = common.EmptyEventID
ms.executionInfo.LastFirstEventId = startEvent.GetEventId()
ms.executionInfo.WorkflowTaskVersion = common.EmptyVersion
ms.executionInfo.WorkflowTaskScheduledEventId = common.EmptyEventID
ms.executionInfo.WorkflowTaskStartedEventId = common.EmptyEventID
ms.executionInfo.WorkflowTaskRequestId = emptyUUID
ms.executionInfo.WorkflowTaskTimeout = timestamp.DurationFromSeconds(0)
ms.executionInfo.CronSchedule = event.GetCronSchedule()
if event.ParentWorkflowExecution != nil {
ms.executionInfo.ParentNamespaceId = event.GetParentWorkflowNamespaceId()
ms.executionInfo.ParentWorkflowId = event.ParentWorkflowExecution.GetWorkflowId()
ms.executionInfo.ParentRunId = event.ParentWorkflowExecution.GetRunId()
ms.executionInfo.ParentClock = parentClock
}
if event.ParentInitiatedEventId != 0 {
ms.executionInfo.ParentInitiatedId = event.GetParentInitiatedEventId()
} else {
ms.executionInfo.ParentInitiatedId = common.EmptyEventID
}
if event.ParentInitiatedEventVersion != 0 {
ms.executionInfo.ParentInitiatedVersion = event.GetParentInitiatedEventVersion()
} else {
ms.executionInfo.ParentInitiatedVersion = common.EmptyVersion
}
if event.RootWorkflowExecution != nil {
ms.executionInfo.RootWorkflowId = event.RootWorkflowExecution.GetWorkflowId()
ms.executionInfo.RootRunId = event.RootWorkflowExecution.GetRunId()
} else {
ms.executionInfo.RootWorkflowId = execution.GetWorkflowId()
ms.executionInfo.RootRunId = execution.GetRunId()
}
ms.executionInfo.ExecutionTime = timestamppb.New(
ms.executionInfo.StartTime.AsTime().Add(event.GetFirstWorkflowTaskBackoff().AsDuration()),
)
ms.executionInfo.Attempt = event.GetAttempt()
if !timestamp.TimeValue(event.GetWorkflowExecutionExpirationTime()).IsZero() {
// TODO: for workflow reset case, re-calculate the expiration time instead of reusing the one in the event
ms.executionInfo.WorkflowExecutionExpirationTime = event.GetWorkflowExecutionExpirationTime()
}
var workflowRunTimeoutTime time.Time
workflowRunTimeoutDuration := ms.executionInfo.WorkflowRunTimeout.AsDuration()
// if workflowRunTimeoutDuration == 0 then the workflowRunTimeoutTime will be 0
// meaning that there is not workflow run timeout
if workflowRunTimeoutDuration != 0 {
firstWorkflowTaskDelayDuration := event.GetFirstWorkflowTaskBackoff().AsDuration()
workflowRunTimeoutDuration = workflowRunTimeoutDuration + firstWorkflowTaskDelayDuration
workflowRunTimeoutTime = ms.executionInfo.StartTime.AsTime().Add(workflowRunTimeoutDuration)
workflowExecutionTimeoutTime := timestamp.TimeValue(ms.executionInfo.WorkflowExecutionExpirationTime)
if !workflowExecutionTimeoutTime.IsZero() && workflowRunTimeoutTime.After(workflowExecutionTimeoutTime) {
workflowRunTimeoutTime = workflowExecutionTimeoutTime
}
}
ms.executionInfo.WorkflowRunExpirationTime = timestamppb.New(workflowRunTimeoutTime)
if event.RetryPolicy != nil {
ms.executionInfo.HasRetryPolicy = true
ms.executionInfo.RetryBackoffCoefficient = event.RetryPolicy.GetBackoffCoefficient()
ms.executionInfo.RetryInitialInterval = event.RetryPolicy.GetInitialInterval()
ms.executionInfo.RetryMaximumAttempts = event.RetryPolicy.GetMaximumAttempts()
ms.executionInfo.RetryMaximumInterval = event.RetryPolicy.GetMaximumInterval()
ms.executionInfo.RetryNonRetryableErrorTypes = event.RetryPolicy.GetNonRetryableErrorTypes()
}
ms.executionInfo.AutoResetPoints = rolloverAutoResetPointsWithExpiringTime(
event.GetPrevAutoResetPoints(),
event.GetContinuedExecutionRunId(),
timestamp.TimeValue(startEvent.GetEventTime()),
ms.namespaceEntry.Retention(),
)
if event.Memo != nil {
ms.executionInfo.Memo = event.Memo.GetFields()
}
if event.SearchAttributes != nil {
ms.executionInfo.SearchAttributes = event.SearchAttributes.GetIndexedFields()
}
if inheritedBuildId := event.InheritedBuildId; inheritedBuildId != "" {
ms.executionInfo.InheritedBuildId = inheritedBuildId
if err := ms.UpdateBuildIdAssignment(inheritedBuildId); err != nil {
return err
}
} else if event.SourceVersionStamp.GetUseVersioning() && event.SourceVersionStamp.GetBuildId() != "" {
// TODO: [cleanup-old-wv]
limit := ms.config.SearchAttributesSizeOfValueLimit(string(ms.namespaceEntry.Name()))
if _, err := ms.addBuildIdToSearchAttributesWithNoVisibilityTask(event.SourceVersionStamp, limit); err != nil {
return err
}
}
if inheritedBuildId := event.InheritedBuildId; inheritedBuildId != "" {
ms.executionInfo.InheritedBuildId = inheritedBuildId
if err := ms.UpdateBuildIdAssignment(inheritedBuildId); err != nil {
return err
}
}
ms.executionInfo.MostRecentWorkerVersionStamp = event.SourceVersionStamp
ms.approximateSize += ms.executionInfo.Size()
ms.writeEventToCache(startEvent)
return nil
}
// AddFirstWorkflowTaskScheduled adds the first workflow task scehduled event unless it should be delayed as indicated
// by the startEvent's FirstWorkflowTaskBackoff.
// Returns the workflow task's scheduled event ID if a task was scheduled, 0 otherwise.
func (ms *MutableStateImpl) AddFirstWorkflowTaskScheduled(
parentClock *clockspb.VectorClock,
startEvent *historypb.HistoryEvent,
bypassTaskGeneration bool,
) (int64, error) {
opTag := tag.WorkflowActionWorkflowTaskScheduled
if err := ms.checkMutability(opTag); err != nil {
return common.EmptyEventID, err
}
scheduleEventID, err := ms.workflowTaskManager.AddFirstWorkflowTaskScheduled(startEvent, bypassTaskGeneration)
if err != nil {
return 0, err
}
if parentClock != nil {
ms.executionInfo.ParentClock = parentClock
}
return scheduleEventID, nil
}
func (ms *MutableStateImpl) AddWorkflowTaskScheduledEvent(
bypassTaskGeneration bool,
workflowTaskType enumsspb.WorkflowTaskType,
) (*WorkflowTaskInfo, error) {
opTag := tag.WorkflowActionWorkflowTaskScheduled
if err := ms.checkMutability(opTag); err != nil {
return nil, err
}
return ms.workflowTaskManager.AddWorkflowTaskScheduledEvent(bypassTaskGeneration, workflowTaskType)
}
// AddWorkflowTaskScheduledEventAsHeartbeat is to record the first WorkflowTaskScheduledEvent during workflow task heartbeat.
func (ms *MutableStateImpl) AddWorkflowTaskScheduledEventAsHeartbeat(
bypassTaskGeneration bool,
originalScheduledTimestamp *timestamppb.Timestamp,
workflowTaskType enumsspb.WorkflowTaskType,
) (*WorkflowTaskInfo, error) {
opTag := tag.WorkflowActionWorkflowTaskScheduled
if err := ms.checkMutability(opTag); err != nil {
return nil, err
}
return ms.workflowTaskManager.AddWorkflowTaskScheduledEventAsHeartbeat(bypassTaskGeneration, originalScheduledTimestamp, workflowTaskType)
}
func (ms *MutableStateImpl) ApplyTransientWorkflowTaskScheduled() (*WorkflowTaskInfo, error) {
return ms.workflowTaskManager.ApplyTransientWorkflowTaskScheduled()
}
func (ms *MutableStateImpl) ApplyWorkflowTaskScheduledEvent(
version int64,
scheduledEventID int64,
taskQueue *taskqueuepb.TaskQueue,
startToCloseTimeout *durationpb.Duration,
attempt int32,
scheduleTimestamp *timestamppb.Timestamp,
originalScheduledTimestamp *timestamppb.Timestamp,
workflowTaskType enumsspb.WorkflowTaskType,
) (*WorkflowTaskInfo, error) {
return ms.workflowTaskManager.ApplyWorkflowTaskScheduledEvent(version, scheduledEventID, taskQueue, startToCloseTimeout, attempt, scheduleTimestamp, originalScheduledTimestamp, workflowTaskType)
}
func (ms *MutableStateImpl) AddWorkflowTaskStartedEvent(
scheduledEventID int64,
requestID string,
taskQueue *taskqueuepb.TaskQueue,
identity string,
versioningStamp *commonpb.WorkerVersionStamp,
) (*historypb.HistoryEvent, *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)
}
func (ms *MutableStateImpl) ApplyWorkflowTaskStartedEvent(
workflowTask *WorkflowTaskInfo,
version int64,
scheduledEventID int64,
startedEventID int64,
requestID string,
timestamp time.Time,
suggestContinueAsNew bool,
historySizeBytes int64,
versioningStamp *commonpb.WorkerVersionStamp,
redirectCounter int64,
) (*WorkflowTaskInfo, error) {
return ms.workflowTaskManager.ApplyWorkflowTaskStartedEvent(workflowTask, version, scheduledEventID,
startedEventID, requestID, timestamp, suggestContinueAsNew, historySizeBytes, versioningStamp, redirectCounter)
}
// TODO (alex-update): Transient needs to be renamed to "TransientOrSpeculative"
func (ms *MutableStateImpl) GetTransientWorkflowTaskInfo(
workflowTask *WorkflowTaskInfo,
identity string,
) *historyspb.TransientWorkflowTaskInfo {
if !ms.IsTransientWorkflowTask() && workflowTask.Type != enumsspb.WORKFLOW_TASK_TYPE_SPECULATIVE {
return nil
}
return ms.workflowTaskManager.GetTransientWorkflowTaskInfo(workflowTask, identity)
}
func (ms *MutableStateImpl) updateBinaryChecksumSearchAttribute() error {
exeInfo := ms.executionInfo
resetPoints := exeInfo.AutoResetPoints.Points
// List of all recent binary checksums associated with the workflow.
recentBinaryChecksums := make([]string, 0, len(resetPoints))
for _, rp := range resetPoints {
if rp.BinaryChecksum != "" {
recentBinaryChecksums = append(recentBinaryChecksums, rp.BinaryChecksum)
}
}
checksumsPayload, err := searchattribute.EncodeValue(recentBinaryChecksums, enumspb.INDEXED_VALUE_TYPE_KEYWORD_LIST)
if err != nil {
return err
}
if exeInfo.SearchAttributes == nil {
exeInfo.SearchAttributes = make(map[string]*commonpb.Payload, 1)
}
if proto.Equal(exeInfo.SearchAttributes[searchattribute.BinaryChecksums], checksumsPayload) {
return nil // unchanged
}
exeInfo.SearchAttributes[searchattribute.BinaryChecksums] = checksumsPayload
return ms.taskGenerator.GenerateUpsertVisibilityTask()
}
// Add a reset point for current task completion if needed.
// Returns true if the reset point was added or false if there was no need or no ability to add.
// Note that a new reset point is added when the pair <binaryChecksum, buildId> changes.
func (ms *MutableStateImpl) addResetPointFromCompletion(
binaryChecksum string,
buildId string,
eventID int64,
maxResetPoints int,
) bool {
resetPoints := ms.executionInfo.AutoResetPoints.GetPoints()
for _, rp := range resetPoints {
if rp.GetBinaryChecksum() == binaryChecksum && rp.GetBuildId() == buildId {
return false
}
}
newPoint := &workflowpb.ResetPointInfo{
BinaryChecksum: binaryChecksum,
BuildId: buildId,
RunId: ms.executionState.GetRunId(),
FirstWorkflowTaskCompletedId: eventID,
CreateTime: timestamppb.New(ms.timeSource.Now()),
Resettable: ms.CheckResettable() == nil,
}
ms.executionInfo.AutoResetPoints = &workflowpb.ResetPoints{
Points: util.SliceTail(append(resetPoints, newPoint), maxResetPoints),
}
return true
}
// ApplyBuildIdRedirect used for applying redirects from History events when rebuilding MS
func (ms *MutableStateImpl) ApplyBuildIdRedirect(buildId string, redirectCounter int64) error {
if ms.GetExecutionInfo().GetBuildIdRedirectCounter() >= redirectCounter {
// Existing redirect counter is more than the given one, so ignore this redirect because
// this or a more recent one has already been applied. This can happen when replaying
// history because redirects can be applied at activity started time, but we don't record
// the actual order of activity started events in history (they're transient tasks).
return nil
}
err := ms.UpdateBuildIdAssignment(buildId)
if err != nil {
return err
}
// if this redirect is applicable, we need to set workflow's redirect counter to the given value
ms.GetExecutionInfo().BuildIdRedirectCounter = redirectCounter
return nil
}
// UpdateBuildIdAssignment based on initial assignment or a redirect
func (ms *MutableStateImpl) UpdateBuildIdAssignment(buildId string) error {
if ms.GetAssignedBuildId() == buildId {
return nil
}
if ms.GetAssignedBuildId() != "" {
// if build ID is being set for the first time, we don't increment redirect counter
ms.GetExecutionInfo().BuildIdRedirectCounter++
}
ms.executionInfo.AssignedBuildId = buildId
// because build id is changed, we clear sticky queue so to make sure the next wf task does not go to old version.
ms.ClearStickyTaskQueue()
limit := ms.config.SearchAttributesSizeOfValueLimit(ms.namespaceEntry.Name().String())
return ms.updateBuildIdsSearchAttribute(&commonpb.WorkerVersionStamp{UseVersioning: true, BuildId: buildId}, limit)
}
func (ms *MutableStateImpl) updateBuildIdsSearchAttribute(stamp *commonpb.WorkerVersionStamp, maxSearchAttributeValueSize int) error {
changed, err := ms.addBuildIdToSearchAttributesWithNoVisibilityTask(stamp, maxSearchAttributeValueSize)
if err != nil {
return err
} else if !changed {
return nil
}
return ms.taskGenerator.GenerateUpsertVisibilityTask()
}
func (ms *MutableStateImpl) loadBuildIds() ([]string, error) {
searchAttributes := ms.executionInfo.SearchAttributes
if searchAttributes == nil {
return []string{}, nil
}
saPayload, found := searchAttributes[searchattribute.BuildIds]
if !found {
return []string{}, nil
}
decoded, err := searchattribute.DecodeValue(saPayload, enumspb.INDEXED_VALUE_TYPE_KEYWORD_LIST, true)
if err != nil {
return nil, err
}
searchAttributeValues, ok := decoded.([]string)
if !ok {
return nil, serviceerror.NewInternal("invalid search attribute value stored for BuildIds")
}
return searchAttributeValues, nil
}
// Takes a list of loaded build IDs from a search attribute and adds a new build ID to it. Also makes sure that the
// resulting SA list begins with either "unversioned" or "assigned:<bld>" based on workflow's Build ID assignment status.
// Returns a potentially modified list.
// [cleanup-old-wv] old versioning does not add "assigned:<bld>" value to the SA.
func (ms *MutableStateImpl) addBuildIdToLoadedSearchAttribute(
existingValues []string,
stamp *commonpb.WorkerVersionStamp,
) []string {
var newValues []string
if !stamp.GetUseVersioning() {
newValues = append(newValues, worker_versioning.UnversionedSearchAttribute)
} else if ms.GetAssignedBuildId() != "" {
newValues = append(newValues, worker_versioning.AssignedBuildIdSearchAttribute(ms.GetAssignedBuildId()))
}
buildId := worker_versioning.VersionStampToBuildIdSearchAttribute(stamp)
found := slices.Contains(newValues, buildId)
for _, existingValue := range existingValues {
if existingValue == buildId {
found = true
}
if !worker_versioning.IsUnversionedOrAssignedBuildIdSearchAttribute(existingValue) {
newValues = append(newValues, existingValue)
}
}
if !found {
newValues = append(newValues, buildId)
}
return newValues
}
func (ms *MutableStateImpl) saveBuildIds(buildIds []string, maxSearchAttributeValueSize int) error {
searchAttributes := ms.executionInfo.SearchAttributes
if searchAttributes == nil {
searchAttributes = make(map[string]*commonpb.Payload, 1)
ms.executionInfo.SearchAttributes = searchAttributes
}
hasUnversionedOrAssigned := worker_versioning.IsUnversionedOrAssignedBuildIdSearchAttribute(buildIds[0])
for {
saPayload, err := searchattribute.EncodeValue(buildIds, enumspb.INDEXED_VALUE_TYPE_KEYWORD_LIST)
if err != nil {
return err
}
if len(buildIds) == 0 || len(saPayload.GetData()) <= maxSearchAttributeValueSize {
searchAttributes[searchattribute.BuildIds] = saPayload
break
}
if len(buildIds) == 1 {
buildIds = make([]string, 0)
} else if hasUnversionedOrAssigned {
// Make sure to maintain the unversioned sentinel, it's required for the reachability API
buildIds = append(buildIds[:1], buildIds[2:]...)
} else {
buildIds = buildIds[1:]
}
}
return nil
}
func (ms *MutableStateImpl) addBuildIdToSearchAttributesWithNoVisibilityTask(stamp *commonpb.WorkerVersionStamp, maxSearchAttributeValueSize int) (bool, error) {
existingBuildIds, err := ms.loadBuildIds()
if err != nil {
return false, err
}
modifiedBuildIds := ms.addBuildIdToLoadedSearchAttribute(existingBuildIds, stamp)
if slices.Equal(existingBuildIds, modifiedBuildIds) {
return false, nil
}
return true, ms.saveBuildIds(modifiedBuildIds, maxSearchAttributeValueSize)
}
// TODO: we will release the restriction when reset API allow those pending
// CheckResettable check if workflow can be reset
func (ms *MutableStateImpl) CheckResettable() error {
if len(ms.GetPendingChildExecutionInfos()) > 0 {
return serviceerror.NewInvalidArgument("it is not allowed resetting to a point that workflow has pending child workflow.")
}
if len(ms.GetPendingRequestCancelExternalInfos()) > 0 {
return serviceerror.NewInvalidArgument("it is not allowed resetting to a point that workflow has pending request cancel.")
}
if len(ms.GetPendingSignalExternalInfos()) > 0 {
return serviceerror.NewInvalidArgument("it is not allowed resetting to a point that workflow has pending signals to send.")
}
return nil
}
func (ms *MutableStateImpl) AddWorkflowTaskCompletedEvent(
workflowTask *WorkflowTaskInfo,
request *workflowservice.RespondWorkflowTaskCompletedRequest,
limits WorkflowTaskCompletionLimits,
) (*historypb.HistoryEvent, error) {
opTag := tag.WorkflowActionWorkflowTaskCompleted
if err := ms.checkMutability(opTag); err != nil {
return nil, err
}
return ms.workflowTaskManager.AddWorkflowTaskCompletedEvent(workflowTask, request, limits)
}
func (ms *MutableStateImpl) ApplyWorkflowTaskCompletedEvent(
event *historypb.HistoryEvent,
) error {
return ms.workflowTaskManager.ApplyWorkflowTaskCompletedEvent(event)
}
func (ms *MutableStateImpl) AddWorkflowTaskTimedOutEvent(
workflowTask *WorkflowTaskInfo,
) (*historypb.HistoryEvent, error) {
opTag := tag.WorkflowActionWorkflowTaskTimedOut
if err := ms.checkMutability(opTag); err != nil {
return nil, err
}
return ms.workflowTaskManager.AddWorkflowTaskTimedOutEvent(workflowTask)
}
func (ms *MutableStateImpl) ApplyWorkflowTaskTimedOutEvent(
timeoutType enumspb.TimeoutType,
) error {
return ms.workflowTaskManager.ApplyWorkflowTaskTimedOutEvent(timeoutType)
}
func (ms *MutableStateImpl) AddWorkflowTaskScheduleToStartTimeoutEvent(
workflowTask *WorkflowTaskInfo,
) (*historypb.HistoryEvent, error) {
opTag := tag.WorkflowActionWorkflowTaskTimedOut
if err := ms.checkMutability(opTag); err != nil {
return nil, err
}
return ms.workflowTaskManager.AddWorkflowTaskScheduleToStartTimeoutEvent(workflowTask)
}
func (ms *MutableStateImpl) AddWorkflowTaskFailedEvent(
workflowTask *WorkflowTaskInfo,
cause enumspb.WorkflowTaskFailedCause,
failure *failurepb.Failure,
identity string,
versioningStamp *commonpb.WorkerVersionStamp,
binChecksum, baseRunID, newRunID string,
forkEventVersion int64,
) (*historypb.HistoryEvent, error) {
opTag := tag.WorkflowActionWorkflowTaskFailed
if err := ms.checkMutability(opTag); err != nil {
return nil, err
}
return ms.workflowTaskManager.AddWorkflowTaskFailedEvent(
workflowTask,
cause,
failure,
identity,
versioningStamp,
binChecksum,
baseRunID,
newRunID,
forkEventVersion,
)
}
func (ms *MutableStateImpl) ApplyWorkflowTaskFailedEvent() error {
return ms.workflowTaskManager.ApplyWorkflowTaskFailedEvent()
}
func (ms *MutableStateImpl) AddActivityTaskScheduledEvent(
workflowTaskCompletedEventID int64,
command *commandpb.ScheduleActivityTaskCommandAttributes,
bypassTaskGeneration bool,
) (*historypb.HistoryEvent, *persistencespb.ActivityInfo, error) {
opTag := tag.WorkflowActionActivityTaskScheduled
if err := ms.checkMutability(opTag); err != nil {
return nil, nil, err
}
_, ok := ms.GetActivityByActivityID(command.GetActivityId())
if ok {
ms.logger.Warn(mutableStateInvalidHistoryActionMsg, opTag,
tag.WorkflowEventID(ms.GetNextEventID()),
tag.ErrorTypeInvalidHistoryAction)
return nil, nil, ms.createCallerError(opTag, "ActivityID: "+command.GetActivityId())
}
event := ms.hBuilder.AddActivityTaskScheduledEvent(workflowTaskCompletedEventID, command)
ai, err := ms.ApplyActivityTaskScheduledEvent(workflowTaskCompletedEventID, event)
// TODO merge active & passive task generation
if !bypassTaskGeneration {
if err := ms.taskGenerator.GenerateActivityTasks(
event,
); err != nil {
return nil, nil, err
}
}
return event, ai, err
}
func (ms *MutableStateImpl) ApplyActivityTaskScheduledEvent(
firstEventID int64,
event *historypb.HistoryEvent,
) (*persistencespb.ActivityInfo, error) {
attributes := event.GetActivityTaskScheduledEventAttributes()
scheduledEventID := event.GetEventId()
scheduleToCloseTimeout := attributes.GetScheduleToCloseTimeout()
ai := &persistencespb.ActivityInfo{
Version: event.GetVersion(),
ScheduledEventId: scheduledEventID,
ScheduledEventBatchId: firstEventID,
ScheduledTime: event.GetEventTime(),
StartedEventId: common.EmptyEventID,
StartedTime: nil,
ActivityId: attributes.ActivityId,
ScheduleToStartTimeout: attributes.GetScheduleToStartTimeout(),
ScheduleToCloseTimeout: scheduleToCloseTimeout,
StartToCloseTimeout: attributes.GetStartToCloseTimeout(),
HeartbeatTimeout: attributes.GetHeartbeatTimeout(),
CancelRequested: false,
CancelRequestId: common.EmptyEventID,
LastHeartbeatUpdateTime: nil,
TimerTaskStatus: TimerTaskStatusNone,
TaskQueue: attributes.TaskQueue.GetName(),
HasRetryPolicy: attributes.RetryPolicy != nil,
Attempt: 1,
ActivityType: attributes.GetActivityType(),
}
if attributes.UseWorkflowBuildId {
if ms.GetAssignedBuildId() != "" {
// only set when using new versioning
ai.BuildIdInfo = &persistencespb.ActivityInfo_UseWorkflowBuildIdInfo_{
UseWorkflowBuildIdInfo: &persistencespb.ActivityInfo_UseWorkflowBuildIdInfo{},
}
} else {
// only set when using old versioning
ai.UseCompatibleVersion = true
}
}
if ai.HasRetryPolicy {
ai.RetryInitialInterval = attributes.RetryPolicy.GetInitialInterval()
ai.RetryBackoffCoefficient = attributes.RetryPolicy.GetBackoffCoefficient()
ai.RetryMaximumInterval = attributes.RetryPolicy.GetMaximumInterval()
ai.RetryMaximumAttempts = attributes.RetryPolicy.GetMaximumAttempts()
ai.RetryNonRetryableErrorTypes = attributes.RetryPolicy.NonRetryableErrorTypes
if scheduleToCloseTimeout.AsDuration() > 0 {
ai.RetryExpirationTime = timestamppb.New(
ai.ScheduledTime.AsTime().Add(scheduleToCloseTimeout.AsDuration()),
)
} else {
ai.RetryExpirationTime = nil
}
}
ms.pendingActivityInfoIDs[ai.ScheduledEventId] = ai
ms.pendingActivityIDToEventID[ai.ActivityId] = ai.ScheduledEventId
ms.updateActivityInfos[ai.ScheduledEventId] = ai
ms.approximateSize += ai.Size() + int64SizeBytes
ms.executionInfo.ActivityCount++
ms.writeEventToCache(event)
return ai, nil
}
func (ms *MutableStateImpl) addStartedEventForTransientActivity(
scheduledEventID int64,
versioningStamp *commonpb.WorkerVersionStamp,
) error {
ai, ok := ms.GetActivityInfo(scheduledEventID)
if !ok || ai.StartedEventId != common.TransientEventID {
return nil
}
if versioningStamp == nil {
// We want to add version stamp to the started event being added now with delay. The task may be now completed,
// failed, cancelled, or timed out. For some cases such as timeout, cancellation, and failed by Resetter we do
// not receive a stamp because worker is not involved, therefore, we reconstruct the stamp base on pending
// activity data, if versioning is used.
// Find the build ID of the worker to whom the task was dispatched base on whether it was an independently-
// assigned activity or not.
startedBuildId := ai.GetLastIndependentlyAssignedBuildId()
if useWf := ai.GetUseWorkflowBuildIdInfo(); useWf != nil {
startedBuildId = useWf.GetLastUsedBuildId()
}
if startedBuildId != "" {
// If a build ID is found, i.e. versioning is used for the activity, set versioning stamp.
versioningStamp = &commonpb.WorkerVersionStamp{UseVersioning: true, BuildId: startedBuildId}
}
}
// activity task was started (as transient event), we need to add it now.
event := ms.hBuilder.AddActivityTaskStartedEvent(
scheduledEventID,
ai.Attempt,
ai.RequestId,
ai.StartedIdentity,
ai.RetryLastFailure,
versioningStamp,
ai.GetUseWorkflowBuildIdInfo().GetLastRedirectCounter(),
)
if ai.StartedTime != nil {
// overwrite started event time to the one recorded in ActivityInfo
event.EventTime = ai.StartedTime
}
return ms.ApplyActivityTaskStartedEvent(event)
}
func (ms *MutableStateImpl) AddActivityTaskStartedEvent(
ai *persistencespb.ActivityInfo,
scheduledEventID int64,
requestID string,
identity string,
versioningStamp *commonpb.WorkerVersionStamp,
) (*historypb.HistoryEvent, error) {
opTag := tag.WorkflowActionActivityTaskStarted
if err := ms.checkMutability(opTag); err != nil {
return nil, err
}
if buildId := worker_versioning.BuildIdIfUsingVersioning(versioningStamp); buildId != "" {
// note that if versioningStamp.BuildId is present we know it's not an old versioning worker because matching
// does not pass build id for old versioning workers to Record*TaskStart.
// TODO: cleanup this comment [cleanup-old-wv]
if useWf := ai.GetUseWorkflowBuildIdInfo(); useWf != nil {
// when a dependent activity is redirected, we update workflow's assigned build ID as well
err := ms.UpdateBuildIdAssignment(buildId)
if err != nil {
return nil, err
}
useWf.LastRedirectCounter = ms.GetExecutionInfo().GetBuildIdRedirectCounter()
} else if !ai.UseCompatibleVersion {
// this activity is using new versioning and is independently assigned to a build ID.
// Storing that in activity info. (normaly, LastIndependentlyAssignedBuildId should be present already,
// but setting it here again, in case the persistence call after AddTask was failed)
ai.BuildIdInfo = &persistencespb.ActivityInfo_LastIndependentlyAssignedBuildId{
LastIndependentlyAssignedBuildId: buildId,
}
}
}
if !ai.HasRetryPolicy {
event := ms.hBuilder.AddActivityTaskStartedEvent(
scheduledEventID,
ai.Attempt,
requestID,
identity,
ai.RetryLastFailure,
versioningStamp,
ms.GetExecutionInfo().GetBuildIdRedirectCounter(),
)
if err := ms.ApplyActivityTaskStartedEvent(event); err != nil {
return nil, err
}
return event, nil
}
// we might need to retry, so do not append started event just yet,
// instead update mutable state and will record started event when activity task is closed
ai.Version = ms.GetCurrentVersion()
ai.StartedEventId = common.TransientEventID
ai.RequestId = requestID
ai.StartedTime = timestamppb.New(ms.timeSource.Now())
ai.StartedIdentity = identity
if err := ms.UpdateActivity(ai); err != nil {
return nil, err
}
ms.syncActivityTasks[ai.ScheduledEventId] = struct{}{}
return nil, nil
}
func (ms *MutableStateImpl) ApplyActivityTaskStartedEvent(
event *historypb.HistoryEvent,
) error {
attributes := event.GetActivityTaskStartedEventAttributes()
scheduledEventID := attributes.GetScheduledEventId()
ai, ok := ms.GetActivityInfo(scheduledEventID)
if !ok {
ms.logError(
fmt.Sprintf("unable to find activity event id: %v in mutable state", scheduledEventID),
tag.ErrorTypeInvalidMutableStateAction,
)
return ErrMissingActivityInfo
}
ms.approximateSize -= ai.Size()
ai.Version = event.GetVersion()
ai.StartedEventId = event.GetEventId()
ai.RequestId = attributes.GetRequestId()
ai.StartedTime = event.GetEventTime()
ms.updateActivityInfos[ai.ScheduledEventId] = ai
ms.approximateSize += ai.Size()
if buildId := worker_versioning.BuildIdIfUsingVersioning(attributes.GetWorkerVersion()); buildId != "" {
if ai.GetUseWorkflowBuildIdInfo() != nil {
// when a dependent activity is redirected, we update workflow's assigned build ID as well
err := ms.ApplyBuildIdRedirect(buildId, attributes.GetBuildIdRedirectCounter())
if err != nil {
return err
}
} else {
// This activity is not attached to the wf build ID so we store its build ID in activity info.
ai.BuildIdInfo = &persistencespb.ActivityInfo_LastIndependentlyAssignedBuildId{LastIndependentlyAssignedBuildId: buildId}
}
}
return nil
}
func (ms *MutableStateImpl) AddActivityTaskCompletedEvent(
scheduledEventID int64,
startedEventID int64,
request *workflowservice.RespondActivityTaskCompletedRequest,
) (*historypb.HistoryEvent, error) {
opTag := tag.WorkflowActionActivityTaskCompleted
if err := ms.checkMutability(opTag); err != nil {
return nil, err
}
if ai, ok := ms.GetActivityInfo(scheduledEventID); !ok || ai.StartedEventId != startedEventID {
ms.logger.Warn(mutableStateInvalidHistoryActionMsg, opTag,
tag.WorkflowEventID(ms.GetNextEventID()),
tag.ErrorTypeInvalidHistoryAction,
tag.Bool(ok),
tag.WorkflowScheduledEventID(scheduledEventID),
tag.WorkflowStartedEventID(startedEventID))
return nil, ms.createInternalServerError(opTag)
}
if err := ms.addStartedEventForTransientActivity(scheduledEventID, request.WorkerVersion); err != nil {
return nil, err
}
event := ms.hBuilder.AddActivityTaskCompletedEvent(
scheduledEventID,
startedEventID,
request.Identity,
request.Result,
)
if err := ms.ApplyActivityTaskCompletedEvent(event); err != nil {
return nil, err
}
return event, nil
}
func (ms *MutableStateImpl) ApplyActivityTaskCompletedEvent(
event *historypb.HistoryEvent,
) error {
attributes := event.GetActivityTaskCompletedEventAttributes()
scheduledEventID := attributes.GetScheduledEventId()
return ms.DeleteActivity(scheduledEventID)
}
func (ms *MutableStateImpl) AddActivityTaskFailedEvent(
scheduledEventID int64,
startedEventID int64,
failure *failurepb.Failure,
retryState enumspb.RetryState,
identity string,
versioningStamp *commonpb.WorkerVersionStamp,
) (*historypb.HistoryEvent, error) {
opTag := tag.WorkflowActionActivityTaskFailed
if err := ms.checkMutability(opTag); err != nil {
return nil, err
}
if ai, ok := ms.GetActivityInfo(scheduledEventID); !ok || ai.StartedEventId != startedEventID {
ms.logger.Warn(mutableStateInvalidHistoryActionMsg, opTag,
tag.WorkflowEventID(ms.GetNextEventID()),
tag.ErrorTypeInvalidHistoryAction,
tag.Bool(ok),
tag.WorkflowScheduledEventID(scheduledEventID),
tag.WorkflowStartedEventID(startedEventID))
return nil, ms.createInternalServerError(opTag)
}
if err := ms.addStartedEventForTransientActivity(scheduledEventID, versioningStamp); err != nil {
return nil, err
}
event := ms.hBuilder.AddActivityTaskFailedEvent(
scheduledEventID,
startedEventID,
failure,
retryState,
identity,
)
if err := ms.ApplyActivityTaskFailedEvent(event); err != nil {
return nil, err
}
return event, nil
}
func (ms *MutableStateImpl) ApplyActivityTaskFailedEvent(
event *historypb.HistoryEvent,
) error {
attributes := event.GetActivityTaskFailedEventAttributes()
scheduledEventID := attributes.GetScheduledEventId()
return ms.DeleteActivity(scheduledEventID)
}
func (ms *MutableStateImpl) AddActivityTaskTimedOutEvent(
scheduledEventID int64,
startedEventID int64,
timeoutFailure *failurepb.Failure,
retryState enumspb.RetryState,
) (*historypb.HistoryEvent, error) {
opTag := tag.WorkflowActionActivityTaskTimedOut
if err := ms.checkMutability(opTag); err != nil {
return nil, err
}
timeoutType := timeoutFailure.GetTimeoutFailureInfo().GetTimeoutType()
ai, ok := ms.GetActivityInfo(scheduledEventID)
if !ok || ai.StartedEventId != startedEventID || ((timeoutType == enumspb.TIMEOUT_TYPE_START_TO_CLOSE ||
timeoutType == enumspb.TIMEOUT_TYPE_HEARTBEAT) && ai.StartedEventId == common.EmptyEventID) {
ms.logger.Warn(mutableStateInvalidHistoryActionMsg, opTag,
tag.WorkflowEventID(ms.GetNextEventID()),
tag.ErrorTypeInvalidHistoryAction,
tag.Bool(ok),
tag.WorkflowScheduledEventID(ai.ScheduledEventId),
tag.WorkflowStartedEventID(ai.StartedEventId),
tag.WorkflowTimeoutType(timeoutType))
return nil, ms.createInternalServerError(opTag)
}
timeoutFailure.Cause = ai.RetryLastFailure
if err := ms.addStartedEventForTransientActivity(scheduledEventID, nil); err != nil {
return nil, err
}
event := ms.hBuilder.AddActivityTaskTimedOutEvent(
scheduledEventID,
startedEventID,
timeoutFailure,
retryState,
)
if err := ms.ApplyActivityTaskTimedOutEvent(event); err != nil {
return nil, err
}
return event, nil
}
func (ms *MutableStateImpl) ApplyActivityTaskTimedOutEvent(
event *historypb.HistoryEvent,
) error {
attributes := event.GetActivityTaskTimedOutEventAttributes()
scheduledEventID := attributes.GetScheduledEventId()
return ms.DeleteActivity(scheduledEventID)
}
func (ms *MutableStateImpl) AddActivityTaskCancelRequestedEvent(
workflowTaskCompletedEventID int64,
scheduledEventID int64,
_ string,
) (*historypb.HistoryEvent, *persistencespb.ActivityInfo, error) {
opTag := tag.WorkflowActionActivityTaskCancelRequested
if err := ms.checkMutability(opTag); err != nil {
return nil, nil, err
}
ai, ok := ms.GetActivityInfo(scheduledEventID)
if !ok {
// It is possible both started and completed events are buffered for this activity
if !ms.hBuilder.HasActivityFinishEvent(scheduledEventID) {
ms.logWarn(mutableStateInvalidHistoryActionMsg, opTag,
tag.WorkflowEventID(ms.GetNextEventID()),
tag.ErrorTypeInvalidHistoryAction,
tag.Bool(ok),
tag.WorkflowScheduledEventID(scheduledEventID))
return nil, nil, ms.createCallerError(opTag, fmt.Sprintf("ScheduledEventID: %d", scheduledEventID))
}
}
// Check for duplicate cancellation
if ok && ai.CancelRequested {
ms.logWarn(mutableStateInvalidHistoryActionMsg, opTag,
tag.WorkflowEventID(ms.GetNextEventID()),
tag.ErrorTypeInvalidHistoryAction,
tag.Bool(ok),
tag.WorkflowScheduledEventID(scheduledEventID))
return nil, nil, ms.createCallerError(opTag, fmt.Sprintf("ScheduledEventID: %d", scheduledEventID))
}
// At this point we know this is a valid activity cancellation request
actCancelReqEvent := ms.hBuilder.AddActivityTaskCancelRequestedEvent(workflowTaskCompletedEventID, scheduledEventID)
if err := ms.ApplyActivityTaskCancelRequestedEvent(actCancelReqEvent); err != nil {
return nil, nil, err
}
return actCancelReqEvent, ai, nil
}
func (ms *MutableStateImpl) ApplyActivityTaskCancelRequestedEvent(
event *historypb.HistoryEvent,
) error {
attributes := event.GetActivityTaskCancelRequestedEventAttributes()
scheduledEventID := attributes.GetScheduledEventId()
ai, ok := ms.GetActivityInfo(scheduledEventID)
if !ok {
// This will only be called on active cluster if activity info is found in mutable state
// Passive side logic should always have activity info in mutable state if this is called, as the only
// scenario where active side logic could have this event without activity info in mutable state is when
// activity start and complete events are buffered.
return nil
}
ms.approximateSize -= ai.Size()
ai.Version = event.GetVersion()
// - We have the activity dispatched to worker.
// - The activity might not be heartbeat'ing, but the activity can still call RecordActivityHeartBeat()
// to see cancellation while reporting progress of the activity.
ai.CancelRequested = true
ai.CancelRequestId = event.GetEventId()
ms.updateActivityInfos[ai.ScheduledEventId] = ai
ms.approximateSize += ai.Size()
return nil
}
func (ms *MutableStateImpl) AddActivityTaskCanceledEvent(
scheduledEventID int64,
startedEventID int64,
latestCancelRequestedEventID int64,
details *commonpb.Payloads,
identity string,
) (*historypb.HistoryEvent, error) {
opTag := tag.WorkflowActionActivityTaskCanceled
if err := ms.checkMutability(opTag); err != nil {
return nil, err
}
ai, ok := ms.GetActivityInfo(scheduledEventID)
if !ok || ai.StartedEventId != startedEventID {
ms.logWarn(mutableStateInvalidHistoryActionMsg, opTag,
tag.WorkflowEventID(ms.GetNextEventID()),
tag.ErrorTypeInvalidHistoryAction,
tag.WorkflowScheduledEventID(scheduledEventID))
return nil, ms.createInternalServerError(opTag)
}
// Verify cancel request as well.
if !ai.CancelRequested {
ms.logWarn(mutableStateInvalidHistoryActionMsg, opTag,
tag.WorkflowEventID(ms.GetNextEventID()),
tag.ErrorTypeInvalidHistoryAction,
tag.WorkflowScheduledEventID(scheduledEventID),
tag.WorkflowActivityID(ai.ActivityId),
tag.WorkflowStartedEventID(ai.StartedEventId))
return nil, ms.createInternalServerError(opTag)
}
if err := ms.addStartedEventForTransientActivity(scheduledEventID, nil); err != nil {
return nil, err
}
event := ms.hBuilder.AddActivityTaskCanceledEvent(
scheduledEventID,
startedEventID,
latestCancelRequestedEventID,
details,
identity,
)
if err := ms.ApplyActivityTaskCanceledEvent(event); err != nil {
return nil, err
}
return event, nil
}
func (ms *MutableStateImpl) ApplyActivityTaskCanceledEvent(
event *historypb.HistoryEvent,
) error {
attributes := event.GetActivityTaskCanceledEventAttributes()
scheduledEventID := attributes.GetScheduledEventId()
return ms.DeleteActivity(scheduledEventID)
}
func (ms *MutableStateImpl) AddCompletedWorkflowEvent(
workflowTaskCompletedEventID int64,
command *commandpb.CompleteWorkflowExecutionCommandAttributes,
newExecutionRunID string,
) (*historypb.HistoryEvent, error) {
opTag := tag.WorkflowActionWorkflowCompleted
if err := ms.checkMutability(opTag); err != nil {
return nil, err
}
event := ms.hBuilder.AddCompletedWorkflowEvent(workflowTaskCompletedEventID, command, newExecutionRunID)
if err := ms.ApplyWorkflowExecutionCompletedEvent(workflowTaskCompletedEventID, event); err != nil {
return nil, err
}
// TODO merge active & passive task generation
if err := ms.taskGenerator.GenerateWorkflowCloseTasks(
event.GetEventTime().AsTime(),
false,
); err != nil {
return nil, err
}
return event, nil
}
func (ms *MutableStateImpl) ApplyWorkflowExecutionCompletedEvent(
firstEventID int64,
event *historypb.HistoryEvent,
) error {
if err := ms.UpdateWorkflowStateStatus(
enumsspb.WORKFLOW_EXECUTION_STATE_COMPLETED,
enumspb.WORKFLOW_EXECUTION_STATUS_COMPLETED,
); err != nil {
return err
}
ms.executionInfo.CompletionEventBatchId = firstEventID // Used when completion event needs to be loaded from database
ms.executionInfo.NewExecutionRunId = event.GetWorkflowExecutionCompletedEventAttributes().GetNewExecutionRunId()
ms.executionInfo.CloseTime = event.GetEventTime()
ms.ClearStickyTaskQueue()
ms.writeEventToCache(event)
return ms.processCloseCallbacks()
}
func (ms *MutableStateImpl) AddFailWorkflowEvent(
workflowTaskCompletedEventID int64,
retryState enumspb.RetryState,
command *commandpb.FailWorkflowExecutionCommandAttributes,
newExecutionRunID string,
) (*historypb.HistoryEvent, error) {
opTag := tag.WorkflowActionWorkflowFailed
if err := ms.checkMutability(opTag); err != nil {
return nil, err
}
event, batchID := ms.hBuilder.AddFailWorkflowEvent(workflowTaskCompletedEventID, retryState, command, newExecutionRunID)
if err := ms.ApplyWorkflowExecutionFailedEvent(batchID, event); err != nil {
return nil, err
}
// TODO merge active & passive task generation
if err := ms.taskGenerator.GenerateWorkflowCloseTasks(
event.GetEventTime().AsTime(),
false,
); err != nil {
return nil, err
}
return event, nil
}
func (ms *MutableStateImpl) ApplyWorkflowExecutionFailedEvent(
firstEventID int64,
event *historypb.HistoryEvent,
) error {
if err := ms.UpdateWorkflowStateStatus(
enumsspb.WORKFLOW_EXECUTION_STATE_COMPLETED,
enumspb.WORKFLOW_EXECUTION_STATUS_FAILED,
); err != nil {
return err
}
ms.executionInfo.CompletionEventBatchId = firstEventID // Used when completion event needs to be loaded from database
ms.executionInfo.NewExecutionRunId = event.GetWorkflowExecutionFailedEventAttributes().GetNewExecutionRunId()
ms.executionInfo.CloseTime = event.GetEventTime()
ms.ClearStickyTaskQueue()
ms.writeEventToCache(event)
return ms.processCloseCallbacks()
}
func (ms *MutableStateImpl) AddTimeoutWorkflowEvent(
firstEventID int64,
retryState enumspb.RetryState,
newExecutionRunID string,
) (*historypb.HistoryEvent, error) {
opTag := tag.WorkflowActionWorkflowTimeout
if err := ms.checkMutability(opTag); err != nil {
return nil, err
}
event := ms.hBuilder.AddTimeoutWorkflowEvent(retryState, newExecutionRunID)
if err := ms.ApplyWorkflowExecutionTimedoutEvent(firstEventID, event); err != nil {
return nil, err
}
// TODO merge active & passive task generation
if err := ms.taskGenerator.GenerateWorkflowCloseTasks(
event.GetEventTime().AsTime(),
false,
); err != nil {
return nil, err
}
return event, nil
}
func (ms *MutableStateImpl) ApplyWorkflowExecutionTimedoutEvent(
firstEventID int64,
event *historypb.HistoryEvent,
) error {
if err := ms.UpdateWorkflowStateStatus(
enumsspb.WORKFLOW_EXECUTION_STATE_COMPLETED,
enumspb.WORKFLOW_EXECUTION_STATUS_TIMED_OUT,
); err != nil {
return err
}
ms.executionInfo.CompletionEventBatchId = firstEventID // Used when completion event needs to be loaded from database
ms.executionInfo.NewExecutionRunId = event.GetWorkflowExecutionTimedOutEventAttributes().GetNewExecutionRunId()
ms.executionInfo.CloseTime = event.GetEventTime()
ms.ClearStickyTaskQueue()
ms.writeEventToCache(event)
return ms.processCloseCallbacks()
}
func (ms *MutableStateImpl) AddWorkflowExecutionCancelRequestedEvent(
request *historyservice.RequestCancelWorkflowExecutionRequest,
) (*historypb.HistoryEvent, error) {
opTag := tag.WorkflowActionWorkflowCancelRequested
if err := ms.checkMutability(opTag); err != nil {
return nil, err
}
if ms.executionInfo.CancelRequested {
ms.logWarn(mutableStateInvalidHistoryActionMsg, opTag,
tag.WorkflowEventID(ms.GetNextEventID()),
tag.ErrorTypeInvalidHistoryAction,
tag.WorkflowState(ms.executionState.State),
tag.Bool(ms.executionInfo.CancelRequested),
tag.Key(ms.executionInfo.CancelRequestId),
)
return nil, ms.createInternalServerError(opTag)
}
event := ms.hBuilder.AddWorkflowExecutionCancelRequestedEvent(request)
if err := ms.ApplyWorkflowExecutionCancelRequestedEvent(event); err != nil {
return nil, err
}
// Set the CancelRequestID on the active cluster. This information is not part of the history event.
ms.executionInfo.CancelRequestId = request.CancelRequest.GetRequestId()
return event, nil
}
func (ms *MutableStateImpl) ApplyWorkflowExecutionCancelRequestedEvent(
_ *historypb.HistoryEvent,
) error {
ms.executionInfo.CancelRequested = true
return nil
}
func (ms *MutableStateImpl) AddWorkflowExecutionCanceledEvent(
workflowTaskCompletedEventID int64,
command *commandpb.CancelWorkflowExecutionCommandAttributes,
) (*historypb.HistoryEvent, error) {
opTag := tag.WorkflowActionWorkflowCanceled
if err := ms.checkMutability(opTag); err != nil {
return nil, err
}
event := ms.hBuilder.AddWorkflowExecutionCanceledEvent(workflowTaskCompletedEventID, command)
if err := ms.ApplyWorkflowExecutionCanceledEvent(workflowTaskCompletedEventID, event); err != nil {
return nil, err
}
// TODO merge active & passive task generation
if err := ms.taskGenerator.GenerateWorkflowCloseTasks(
event.GetEventTime().AsTime(),
false,
); err != nil {
return nil, err
}
return event, nil
}
func (ms *MutableStateImpl) ApplyWorkflowExecutionCanceledEvent(
firstEventID int64,
event *historypb.HistoryEvent,
) error {
if err := ms.UpdateWorkflowStateStatus(
enumsspb.WORKFLOW_EXECUTION_STATE_COMPLETED,
enumspb.WORKFLOW_EXECUTION_STATUS_CANCELED,
); err != nil {
return err
}
ms.executionInfo.CompletionEventBatchId = firstEventID // Used when completion event needs to be loaded from database
ms.executionInfo.NewExecutionRunId = ""
ms.executionInfo.CloseTime = event.GetEventTime()
ms.ClearStickyTaskQueue()
ms.writeEventToCache(event)
return ms.processCloseCallbacks()
}
func (ms *MutableStateImpl) AddRequestCancelExternalWorkflowExecutionInitiatedEvent(
workflowTaskCompletedEventID int64,
cancelRequestID string,
command *commandpb.RequestCancelExternalWorkflowExecutionCommandAttributes,
targetNamespaceID namespace.ID,
) (*historypb.HistoryEvent, *persistencespb.RequestCancelInfo, error) {
opTag := tag.WorkflowActionExternalWorkflowCancelInitiated
if err := ms.checkMutability(opTag); err != nil {
return nil, nil, err
}
event := ms.hBuilder.AddRequestCancelExternalWorkflowExecutionInitiatedEvent(workflowTaskCompletedEventID, command, targetNamespaceID)
rci, err := ms.ApplyRequestCancelExternalWorkflowExecutionInitiatedEvent(workflowTaskCompletedEventID, event, cancelRequestID)
if err != nil {
return nil, nil, err
}
// TODO merge active & passive task generation
if err := ms.taskGenerator.GenerateRequestCancelExternalTasks(
event,
); err != nil {
return nil, nil, err
}
return event, rci, nil
}
func (ms *MutableStateImpl) ApplyRequestCancelExternalWorkflowExecutionInitiatedEvent(
firstEventID int64,
event *historypb.HistoryEvent,
cancelRequestID string,
) (*persistencespb.RequestCancelInfo, error) {
// TODO: Evaluate if we need cancelRequestID also part of history event
initiatedEventID := event.GetEventId()
rci := &persistencespb.RequestCancelInfo{
Version: event.GetVersion(),
InitiatedEventBatchId: firstEventID,
InitiatedEventId: initiatedEventID,
CancelRequestId: cancelRequestID,
}
ms.pendingRequestCancelInfoIDs[rci.InitiatedEventId] = rci
ms.updateRequestCancelInfos[rci.InitiatedEventId] = rci
ms.approximateSize += rci.Size() + int64SizeBytes
ms.executionInfo.RequestCancelExternalCount++
ms.writeEventToCache(event)
return rci, nil
}
func (ms *MutableStateImpl) AddExternalWorkflowExecutionCancelRequested(
initiatedID int64,
targetNamespace namespace.Name,
targetNamespaceID namespace.ID,
workflowID string,
runID string,
) (*historypb.HistoryEvent, error) {
opTag := tag.WorkflowActionExternalWorkflowCancelRequested
if err := ms.checkMutability(opTag); err != nil {
return nil, err
}
_, ok := ms.GetRequestCancelInfo(initiatedID)
if !ok {
ms.logWarn(mutableStateInvalidHistoryActionMsg, opTag,
tag.WorkflowEventID(ms.GetNextEventID()),
tag.ErrorTypeInvalidHistoryAction,
tag.WorkflowInitiatedID(initiatedID))
return nil, ms.createInternalServerError(opTag)
}
event := ms.hBuilder.AddExternalWorkflowExecutionCancelRequested(
initiatedID,
targetNamespace,
targetNamespaceID,
workflowID,
runID,
)
if err := ms.ApplyExternalWorkflowExecutionCancelRequested(event); err != nil {
return nil, err
}
return event, nil
}
func (ms *MutableStateImpl) ApplyExternalWorkflowExecutionCancelRequested(
event *historypb.HistoryEvent,
) error {
initiatedID := event.GetExternalWorkflowExecutionCancelRequestedEventAttributes().GetInitiatedEventId()
return ms.DeletePendingRequestCancel(initiatedID)
}
func (ms *MutableStateImpl) AddRequestCancelExternalWorkflowExecutionFailedEvent(
initiatedID int64,
targetNamespace namespace.Name,
targetNamespaceID namespace.ID,
workflowID string,
runID string,
cause enumspb.CancelExternalWorkflowExecutionFailedCause,
) (*historypb.HistoryEvent, error) {
opTag := tag.WorkflowActionExternalWorkflowCancelFailed
if err := ms.checkMutability(opTag); err != nil {
return nil, err
}
_, ok := ms.GetRequestCancelInfo(initiatedID)
if !ok {
ms.logWarn(mutableStateInvalidHistoryActionMsg, opTag,
tag.WorkflowEventID(ms.GetNextEventID()),
tag.ErrorTypeInvalidHistoryAction,
tag.WorkflowInitiatedID(initiatedID))
return nil, ms.createInternalServerError(opTag)
}
event := ms.hBuilder.AddRequestCancelExternalWorkflowExecutionFailedEvent(
common.EmptyEventID, // TODO this field is not used at all
initiatedID,
targetNamespace,
targetNamespaceID,
workflowID,
runID,
cause,
)
if err := ms.ApplyRequestCancelExternalWorkflowExecutionFailedEvent(event); err != nil {
return nil, err
}
return event, nil
}
func (ms *MutableStateImpl) ApplyRequestCancelExternalWorkflowExecutionFailedEvent(
event *historypb.HistoryEvent,
) error {
initiatedID := event.GetRequestCancelExternalWorkflowExecutionFailedEventAttributes().GetInitiatedEventId()
return ms.DeletePendingRequestCancel(initiatedID)
}
func (ms *MutableStateImpl) AddSignalExternalWorkflowExecutionInitiatedEvent(
workflowTaskCompletedEventID int64,
signalRequestID string,
command *commandpb.SignalExternalWorkflowExecutionCommandAttributes,
targetNamespaceID namespace.ID,
) (*historypb.HistoryEvent, *persistencespb.SignalInfo, error) {
opTag := tag.WorkflowActionExternalWorkflowSignalInitiated
if err := ms.checkMutability(opTag); err != nil {
return nil, nil, err
}
event := ms.hBuilder.AddSignalExternalWorkflowExecutionInitiatedEvent(workflowTaskCompletedEventID, command, targetNamespaceID)
si, err := ms.ApplySignalExternalWorkflowExecutionInitiatedEvent(workflowTaskCompletedEventID, event, signalRequestID)
if err != nil {
return nil, nil, err
}
// TODO merge active & passive task generation
if err := ms.taskGenerator.GenerateSignalExternalTasks(
event,
); err != nil {
return nil, nil, err
}
return event, si, nil
}
func (ms *MutableStateImpl) ApplySignalExternalWorkflowExecutionInitiatedEvent(
firstEventID int64,
event *historypb.HistoryEvent,
signalRequestID string,
) (*persistencespb.SignalInfo, error) {
// TODO: Consider also writing signalRequestID to history event
initiatedEventID := event.GetEventId()
si := &persistencespb.SignalInfo{
Version: event.GetVersion(),
InitiatedEventBatchId: firstEventID,
InitiatedEventId: initiatedEventID,
RequestId: signalRequestID,
}
ms.pendingSignalInfoIDs[si.InitiatedEventId] = si
ms.updateSignalInfos[si.InitiatedEventId] = si
ms.approximateSize += si.Size() + int64SizeBytes
ms.executionInfo.SignalExternalCount++
ms.writeEventToCache(event)
return si, nil
}
func (ms *MutableStateImpl) AddUpsertWorkflowSearchAttributesEvent(
workflowTaskCompletedEventID int64,
command *commandpb.UpsertWorkflowSearchAttributesCommandAttributes,
) (*historypb.HistoryEvent, error) {
opTag := tag.WorkflowActionUpsertWorkflowSearchAttributes
if err := ms.checkMutability(opTag); err != nil {
return nil, err
}
event := ms.hBuilder.AddUpsertWorkflowSearchAttributesEvent(workflowTaskCompletedEventID, command)
ms.ApplyUpsertWorkflowSearchAttributesEvent(event)
// TODO merge active & passive task generation
if err := ms.taskGenerator.GenerateUpsertVisibilityTask(); err != nil {
return nil, err
}
return event, nil
}
func (ms *MutableStateImpl) ApplyUpsertWorkflowSearchAttributesEvent(
event *historypb.HistoryEvent,
) {
upsertSearchAttr := event.GetUpsertWorkflowSearchAttributesEventAttributes().GetSearchAttributes().GetIndexedFields()
ms.approximateSize -= ms.executionInfo.Size()
ms.executionInfo.SearchAttributes = payload.MergeMapOfPayload(ms.executionInfo.SearchAttributes, upsertSearchAttr)
ms.approximateSize += ms.executionInfo.Size()
}
func (ms *MutableStateImpl) AddWorkflowPropertiesModifiedEvent(
workflowTaskCompletedEventID int64,
command *commandpb.ModifyWorkflowPropertiesCommandAttributes,
) (*historypb.HistoryEvent, error) {
opTag := tag.WorkflowActionWorkflowPropertiesModified
if err := ms.checkMutability(opTag); err != nil {
return nil, err
}
event := ms.hBuilder.AddWorkflowPropertiesModifiedEvent(workflowTaskCompletedEventID, command)
ms.ApplyWorkflowPropertiesModifiedEvent(event)
// TODO merge active & passive task generation
if err := ms.taskGenerator.GenerateUpsertVisibilityTask(); err != nil {
return nil, err
}
return event, nil
}
func (ms *MutableStateImpl) ApplyWorkflowPropertiesModifiedEvent(
event *historypb.HistoryEvent,
) {
attr := event.GetWorkflowPropertiesModifiedEventAttributes()
if attr.UpsertedMemo != nil {
upsertMemo := attr.GetUpsertedMemo().GetFields()
ms.approximateSize -= ms.executionInfo.Size()
ms.executionInfo.Memo = payload.MergeMapOfPayload(ms.executionInfo.Memo, upsertMemo)
ms.approximateSize += ms.executionInfo.Size()
}
}
func (ms *MutableStateImpl) AddExternalWorkflowExecutionSignaled(
initiatedID int64,
targetNamespace namespace.Name,
targetNamespaceID namespace.ID,
workflowID string,
runID string,
control string, // TODO this field is probably deprecated
) (*historypb.HistoryEvent, error) {
opTag := tag.WorkflowActionExternalWorkflowSignalRequested
if err := ms.checkMutability(opTag); err != nil {
return nil, err
}
_, ok := ms.GetSignalInfo(initiatedID)
if !ok {
ms.logWarn(mutableStateInvalidHistoryActionMsg, opTag,
tag.WorkflowEventID(ms.GetNextEventID()),
tag.ErrorTypeInvalidHistoryAction,
tag.WorkflowInitiatedID(initiatedID))
return nil, ms.createInternalServerError(opTag)
}
event := ms.hBuilder.AddExternalWorkflowExecutionSignaled(
initiatedID,
targetNamespace,
targetNamespaceID,
workflowID,
runID,
control, // TODO this field is probably deprecated
)
if err := ms.ApplyExternalWorkflowExecutionSignaled(event); err != nil {
return nil, err
}
return event, nil
}
func (ms *MutableStateImpl) ApplyExternalWorkflowExecutionSignaled(
event *historypb.HistoryEvent,
) error {
initiatedID := event.GetExternalWorkflowExecutionSignaledEventAttributes().GetInitiatedEventId()
return ms.DeletePendingSignal(initiatedID)
}
func (ms *MutableStateImpl) AddSignalExternalWorkflowExecutionFailedEvent(
initiatedID int64,
targetNamespace namespace.Name,
targetNamespaceID namespace.ID,
workflowID string,
runID string,
control string, // TODO this field is probably deprecated
cause enumspb.SignalExternalWorkflowExecutionFailedCause,
) (*historypb.HistoryEvent, error) {
opTag := tag.WorkflowActionExternalWorkflowSignalFailed
if err := ms.checkMutability(opTag); err != nil {
return nil, err
}
_, ok := ms.GetSignalInfo(initiatedID)
if !ok {
ms.logWarn(mutableStateInvalidHistoryActionMsg, opTag,
tag.WorkflowEventID(ms.GetNextEventID()),
tag.ErrorTypeInvalidHistoryAction,
tag.WorkflowInitiatedID(initiatedID))
return nil, ms.createInternalServerError(opTag)
}
event := ms.hBuilder.AddSignalExternalWorkflowExecutionFailedEvent(
common.EmptyEventID, // TODO this field is not used at all
initiatedID,
targetNamespace,
targetNamespaceID,
workflowID,
runID,
control, // TODO this field is probably deprecated
cause,
)
if err := ms.ApplySignalExternalWorkflowExecutionFailedEvent(event); err != nil {
return nil, err
}
return event, nil
}
func (ms *MutableStateImpl) ApplySignalExternalWorkflowExecutionFailedEvent(
event *historypb.HistoryEvent,
) error {
initiatedID := event.GetSignalExternalWorkflowExecutionFailedEventAttributes().GetInitiatedEventId()
return ms.DeletePendingSignal(initiatedID)
}
func (ms *MutableStateImpl) AddTimerStartedEvent(
workflowTaskCompletedEventID int64,
command *commandpb.StartTimerCommandAttributes,
) (*historypb.HistoryEvent, *persistencespb.TimerInfo, error) {
opTag := tag.WorkflowActionTimerStarted
if err := ms.checkMutability(opTag); err != nil {
return nil, nil, err
}
timerID := command.GetTimerId()
_, ok := ms.GetUserTimerInfo(timerID)
if ok {
ms.logWarn(mutableStateInvalidHistoryActionMsg, opTag,
tag.WorkflowEventID(ms.GetNextEventID()),
tag.ErrorTypeInvalidHistoryAction,
tag.WorkflowTimerID(timerID))
return nil, nil, ms.createCallerError(opTag, "TimerID: "+command.GetTimerId())
}
event := ms.hBuilder.AddTimerStartedEvent(workflowTaskCompletedEventID, command)
ti, err := ms.ApplyTimerStartedEvent(event)
if err != nil {
return nil, nil, err
}
return event, ti, err
}
func (ms *MutableStateImpl) ApplyTimerStartedEvent(
event *historypb.HistoryEvent,
) (*persistencespb.TimerInfo, error) {
attributes := event.GetTimerStartedEventAttributes()
timerID := attributes.GetTimerId()
startToFireTimeout := attributes.GetStartToFireTimeout().AsDuration()
// TODO: Time skew needs to be taken in to account.
expiryTime := timestamp.TimeValue(event.GetEventTime()).Add(startToFireTimeout)
ti := &persistencespb.TimerInfo{
Version: event.GetVersion(),
TimerId: timerID,
ExpiryTime: timestamppb.New(expiryTime),
StartedEventId: event.GetEventId(),
TaskStatus: TimerTaskStatusNone,
}
ms.pendingTimerInfoIDs[ti.TimerId] = ti
ms.pendingTimerEventIDToID[ti.StartedEventId] = ti.TimerId
ms.updateTimerInfos[ti.TimerId] = ti
ms.approximateSize += ti.Size() + len(ti.TimerId)
ms.executionInfo.UserTimerCount++
return ti, nil
}
func (ms *MutableStateImpl) AddTimerFiredEvent(
timerID string,
) (*historypb.HistoryEvent, error) {
opTag := tag.WorkflowActionTimerFired
if err := ms.checkMutability(opTag); err != nil {
return nil, err
}
timerInfo, ok := ms.GetUserTimerInfo(timerID)
if !ok {
ms.logWarn(mutableStateInvalidHistoryActionMsg, opTag,
tag.WorkflowEventID(ms.GetNextEventID()),
tag.ErrorTypeInvalidHistoryAction,
tag.WorkflowTimerID(timerID))
return nil, ms.createInternalServerError(opTag)
}
// Timer is running.
event := ms.hBuilder.AddTimerFiredEvent(timerInfo.GetStartedEventId(), timerInfo.TimerId)
if err := ms.ApplyTimerFiredEvent(event); err != nil {
return nil, err
}
return event, nil
}
func (ms *MutableStateImpl) ApplyTimerFiredEvent(
event *historypb.HistoryEvent,
) error {
attributes := event.GetTimerFiredEventAttributes()
timerID := attributes.GetTimerId()
return ms.DeleteUserTimer(timerID)
}
func (ms *MutableStateImpl) AddTimerCanceledEvent(
workflowTaskCompletedEventID int64,
command *commandpb.CancelTimerCommandAttributes,
identity string,
) (*historypb.HistoryEvent, error) {
opTag := tag.WorkflowActionTimerCanceled
if err := ms.checkMutability(opTag); err != nil {
return nil, err
}
var timerStartedEventID int64
timerID := command.GetTimerId()
ti, ok := ms.GetUserTimerInfo(timerID)
if !ok {
// if timer is not running then check if it has fired in the mutable state.
// If so clear the timer from the mutable state. We need to check both the
// bufferedEvents and the history builder
timerFiredEvent := ms.hBuilder.GetAndRemoveTimerFireEvent(timerID)
if timerFiredEvent == nil {
ms.logWarn(mutableStateInvalidHistoryActionMsg, opTag,
tag.WorkflowEventID(ms.GetNextEventID()),
tag.ErrorTypeInvalidHistoryAction,
tag.WorkflowTimerID(timerID))
return nil, ms.createCallerError(opTag, "TimerID: "+command.GetTimerId())
}
timerStartedEventID = timerFiredEvent.GetTimerFiredEventAttributes().GetStartedEventId()
} else {
timerStartedEventID = ti.GetStartedEventId()
}
// Timer is running.
event := ms.hBuilder.AddTimerCanceledEvent(
workflowTaskCompletedEventID,
timerStartedEventID,
timerID,
identity,
)
if ok {
if err := ms.ApplyTimerCanceledEvent(event); err != nil {
return nil, err
}
}
return event, nil
}
func (ms *MutableStateImpl) ApplyTimerCanceledEvent(
event *historypb.HistoryEvent,
) error {
attributes := event.GetTimerCanceledEventAttributes()
timerID := attributes.GetTimerId()
return ms.DeleteUserTimer(timerID)
}
func (ms *MutableStateImpl) AddRecordMarkerEvent(
workflowTaskCompletedEventID int64,
command *commandpb.RecordMarkerCommandAttributes,
) (*historypb.HistoryEvent, error) {
opTag := tag.WorkflowActionWorkflowRecordMarker
if err := ms.checkMutability(opTag); err != nil {
return nil, err
}
return ms.hBuilder.AddMarkerRecordedEvent(workflowTaskCompletedEventID, command), nil
}
func (ms *MutableStateImpl) AddWorkflowExecutionTerminatedEvent(
firstEventID int64,
reason string,
details *commonpb.Payloads,
identity string,
deleteAfterTerminate bool,
) (*historypb.HistoryEvent, error) {
opTag := tag.WorkflowActionWorkflowTerminated
if err := ms.checkMutability(opTag); err != nil {
return nil, err
}
event := ms.hBuilder.AddWorkflowExecutionTerminatedEvent(reason, details, identity)
if err := ms.ApplyWorkflowExecutionTerminatedEvent(firstEventID, event); err != nil {
return nil, err
}
// TODO merge active & passive task generation
if err := ms.taskGenerator.GenerateWorkflowCloseTasks(
event.GetEventTime().AsTime(),
deleteAfterTerminate,
); err != nil {
return nil, err
}
return event, nil
}
// AddWorkflowExecutionUpdateAdmittedEvent adds a WorkflowExecutionUpdateAdmittedEvent to in-memory history.
func (ms *MutableStateImpl) AddWorkflowExecutionUpdateAdmittedEvent(request *updatepb.Request, origin enumspb.UpdateAdmittedEventOrigin) (*historypb.HistoryEvent, error) {
if err := ms.checkMutability(tag.WorkflowActionUpdateAdmitted); err != nil {
return nil, err
}
event, batchId := ms.hBuilder.AddWorkflowExecutionUpdateAdmittedEvent(request, origin)
if err := ms.ApplyWorkflowExecutionUpdateAdmittedEvent(event, batchId); err != nil {
return nil, err
}
return event, nil
}
// ApplyWorkflowExecutionUpdateAdmittedEvent applies a WorkflowExecutionUpdateAdmittedEvent to mutable state.
func (ms *MutableStateImpl) ApplyWorkflowExecutionUpdateAdmittedEvent(event *historypb.HistoryEvent, batchId int64) error {
attrs := event.GetWorkflowExecutionUpdateAdmittedEventAttributes()
if attrs == nil {
return serviceerror.NewInternal("wrong event type in call to ApplyWorkflowExecutionUpdateAdmittedEvent")
}
if ms.executionInfo.UpdateInfos == nil {
ms.executionInfo.UpdateInfos = make(map[string]*updatespb.UpdateInfo, 1)
}
updateID := attrs.GetRequest().GetMeta().GetUpdateId()
admission := &updatespb.UpdateInfo_Admission{
Admission: &updatespb.AdmissionInfo{
Location: &updatespb.AdmissionInfo_HistoryPointer_{
HistoryPointer: &updatespb.AdmissionInfo_HistoryPointer{
EventId: event.EventId,
EventBatchId: batchId,
},
},
},
}
if _, ok := ms.executionInfo.UpdateInfos[updateID]; ok {
return serviceerror.NewInternal(fmt.Sprintf("Update ID %s is already present in mutable state", updateID))
}
ui := updatespb.UpdateInfo{Value: admission}
ms.executionInfo.UpdateInfos[updateID] = &ui
ms.executionInfo.UpdateCount++
sizeDelta := ui.Size() + len(updateID)
ms.approximateSize += sizeDelta
ms.writeEventToCache(event)
return nil
}
func (ms *MutableStateImpl) AddWorkflowExecutionUpdateAcceptedEvent(
protocolInstanceID string,
acceptedRequestMessageId string,
acceptedRequestSequencingEventId int64,
acceptedRequest *updatepb.Request,
) (*historypb.HistoryEvent, error) {
if err := ms.checkMutability(tag.WorkflowActionUpdateAccepted); err != nil {
return nil, err
}
event := ms.hBuilder.AddWorkflowExecutionUpdateAcceptedEvent(protocolInstanceID, acceptedRequestMessageId, acceptedRequestSequencingEventId, acceptedRequest)
if err := ms.ApplyWorkflowExecutionUpdateAcceptedEvent(event); err != nil {
return nil, err
}
return event, nil
}
func (ms *MutableStateImpl) ApplyWorkflowExecutionUpdateAcceptedEvent(
event *historypb.HistoryEvent,
) error {
attrs := event.GetWorkflowExecutionUpdateAcceptedEventAttributes()
if attrs == nil {
return serviceerror.NewInternal("wrong event type in call to ApplyWorkflowExecutionUpdateAcceptedEvent")
}
if ms.executionInfo.UpdateInfos == nil {
ms.executionInfo.UpdateInfos = make(map[string]*updatespb.UpdateInfo, 1)
}
updateID := attrs.GetAcceptedRequest().GetMeta().GetUpdateId()
var sizeDelta int
if ui, ok := ms.executionInfo.UpdateInfos[updateID]; ok {
sizeBefore := ui.Size()
ui.Value = &updatespb.UpdateInfo_Acceptance{
Acceptance: &updatespb.AcceptanceInfo{EventId: event.EventId},
}
sizeDelta = ui.Size() - sizeBefore
} else {
ui := updatespb.UpdateInfo{
Value: &updatespb.UpdateInfo_Acceptance{
Acceptance: &updatespb.AcceptanceInfo{EventId: event.EventId},
},
}
ms.executionInfo.UpdateInfos[updateID] = &ui
ms.executionInfo.UpdateCount++
sizeDelta = ui.Size() + len(updateID)
}
ms.approximateSize += sizeDelta
ms.writeEventToCache(event)
return nil
}
func (ms *MutableStateImpl) AddWorkflowExecutionUpdateCompletedEvent(
acceptedEventID int64,
updResp *updatepb.Response,
) (*historypb.HistoryEvent, error) {
if err := ms.checkMutability(tag.WorkflowActionUpdateCompleted); err != nil {
return nil, err
}
event, batchID := ms.hBuilder.AddWorkflowExecutionUpdateCompletedEvent(acceptedEventID, updResp)
if err := ms.ApplyWorkflowExecutionUpdateCompletedEvent(event, batchID); err != nil {
return nil, err
}
return event, nil
}
func (ms *MutableStateImpl) ApplyWorkflowExecutionUpdateCompletedEvent(
event *historypb.HistoryEvent,
batchID int64,
) error {
attrs := event.GetWorkflowExecutionUpdateCompletedEventAttributes()
if attrs == nil {
return serviceerror.NewInternal("wrong event type in call to ApplyWorkflowExecutionUpdateCompletedEvent")
}
if ms.executionInfo.UpdateInfos == nil {
ms.executionInfo.UpdateInfos = make(map[string]*updatespb.UpdateInfo, 1)
}
updateID := attrs.GetMeta().GetUpdateId()
var sizeDelta int
if ui, ok := ms.executionInfo.UpdateInfos[updateID]; ok {
sizeBefore := ui.Size()
ui.Value = &updatespb.UpdateInfo_Completion{
Completion: &updatespb.CompletionInfo{
EventId: event.EventId,
EventBatchId: batchID,
},
}
sizeDelta = ui.Size() - sizeBefore
} else {
// TODO (alex): this should never happened because UpdateInfo should always be created before
// with UpdateAccepted event which MUST preceded UpdateCompleted event.
// Better to return error here!
ui := updatespb.UpdateInfo{
Value: &updatespb.UpdateInfo_Completion{
Completion: &updatespb.CompletionInfo{EventId: event.EventId},
},
}
ms.executionInfo.UpdateInfos[updateID] = &ui
ms.executionInfo.UpdateCount++
sizeDelta = ui.Size() + len(updateID)
}
ms.approximateSize += sizeDelta
ms.writeEventToCache(event)
return nil
}
func (ms *MutableStateImpl) RejectWorkflowExecutionUpdate(_ string, _ *updatepb.Rejection) error {
// TODO (alex-update): This method is noop because we don't currently write rejections to the history.
return nil
}
func (ms *MutableStateImpl) ApplyWorkflowExecutionTerminatedEvent(
firstEventID int64,
event *historypb.HistoryEvent,
) error {
if err := ms.UpdateWorkflowStateStatus(
enumsspb.WORKFLOW_EXECUTION_STATE_COMPLETED,
enumspb.WORKFLOW_EXECUTION_STATUS_TERMINATED,
); err != nil {
return err
}
ms.executionInfo.CompletionEventBatchId = firstEventID // Used when completion event needs to be loaded from database
ms.executionInfo.NewExecutionRunId = ""
ms.executionInfo.CloseTime = event.GetEventTime()
ms.ClearStickyTaskQueue()
ms.writeEventToCache(event)
return ms.processCloseCallbacks()
}
func (ms *MutableStateImpl) AddWorkflowExecutionSignaled(
signalName string,
input *commonpb.Payloads,
identity string,
header *commonpb.Header,
skipGenerateWorkflowTask bool,
) (*historypb.HistoryEvent, error) {
return ms.AddWorkflowExecutionSignaledEvent(
signalName,
input,
identity,
header,
skipGenerateWorkflowTask,
nil,
)
}
func (ms *MutableStateImpl) AddWorkflowExecutionSignaledEvent(
signalName string,
input *commonpb.Payloads,
identity string,
header *commonpb.Header,
skipGenerateWorkflowTask bool,
externalWorkflowExecution *commonpb.WorkflowExecution,
) (*historypb.HistoryEvent, error) {
opTag := tag.WorkflowActionWorkflowSignaled
if err := ms.checkMutability(opTag); err != nil {
return nil, err
}
event := ms.hBuilder.AddWorkflowExecutionSignaledEvent(
signalName,
input,
identity,
header,
skipGenerateWorkflowTask,
externalWorkflowExecution,
)
if err := ms.ApplyWorkflowExecutionSignaled(event); err != nil {
return nil, err
}
return event, nil
}
func (ms *MutableStateImpl) ApplyWorkflowExecutionSignaled(
_ *historypb.HistoryEvent,
) error {
// Increment signal count in mutable state for this workflow execution
ms.executionInfo.SignalCount++
return nil
}
func (ms *MutableStateImpl) AddContinueAsNewEvent(
ctx context.Context,
firstEventID int64,
workflowTaskCompletedEventID int64,
parentNamespace namespace.Name,
command *commandpb.ContinueAsNewWorkflowExecutionCommandAttributes,
) (*historypb.HistoryEvent, MutableState, error) {
opTag := tag.WorkflowActionWorkflowContinueAsNew
if err := ms.checkMutability(opTag); err != nil {
return nil, nil, err
}
var err error
newRunID := uuid.New()
newExecution := commonpb.WorkflowExecution{
WorkflowId: ms.executionInfo.WorkflowId,
RunId: newRunID,
}
// Extract ParentExecutionInfo from current run so it can be passed down to the next
var parentInfo *workflowspb.ParentExecutionInfo
var rootInfo *workflowspb.RootExecutionInfo
if ms.HasParentExecution() {
parentInfo = &workflowspb.ParentExecutionInfo{
NamespaceId: ms.executionInfo.ParentNamespaceId,
Namespace: parentNamespace.String(),
Execution: &commonpb.WorkflowExecution{
WorkflowId: ms.executionInfo.ParentWorkflowId,
RunId: ms.executionInfo.ParentRunId,
},
InitiatedId: ms.executionInfo.ParentInitiatedId,
InitiatedVersion: ms.executionInfo.ParentInitiatedVersion,
Clock: ms.executionInfo.ParentClock,
}
rootInfo = &workflowspb.RootExecutionInfo{
Execution: &commonpb.WorkflowExecution{
WorkflowId: ms.executionInfo.RootWorkflowId,
RunId: ms.executionInfo.RootRunId,
},
}
}
continueAsNewEvent := ms.hBuilder.AddContinuedAsNewEvent(
workflowTaskCompletedEventID,
newRunID,
command,
)
firstRunID, err := ms.GetFirstRunID(ctx)
if err != nil {
return nil, nil, err
}
newMutableState := NewMutableState(
ms.shard,
ms.shard.GetEventsCache(),
ms.logger,
ms.namespaceEntry,
ms.executionInfo.WorkflowId,
newRunID,
timestamp.TimeValue(continueAsNewEvent.GetEventTime()),
)
if _, err = newMutableState.addWorkflowExecutionStartedEventForContinueAsNew(
parentInfo,
&newExecution,
ms,
command,
firstRunID,
rootInfo,
); err != nil {
return nil, nil, err
}
// TODO: should this be in the "Apply" function? How does this work with replication?
// TODO: also copy over close callbacks if workflow completes unsuccessfully and its retry policy permits it.
oldCallbacks := callbacks.MachineCollection(ms.HSM())
newCallbacks := callbacks.MachineCollection(newMutableState.HSM())
for _, node := range oldCallbacks.List() {
cb, err := oldCallbacks.Data(node.Key.ID)
if err != nil {
return nil, nil, err
}
if _, ok := cb.PublicInfo.GetTrigger().GetVariant().(*workflowpb.CallbackInfo_Trigger_WorkflowClosed); ok {
if _, err := newCallbacks.Add(node.Key.ID, cb); err != nil {
return nil, nil, err
}
}
}
if err = newMutableState.SetHistoryTree(
newMutableState.executionInfo.WorkflowExecutionTimeout,
newMutableState.executionInfo.WorkflowRunTimeout,
newRunID,
); err != nil {
return nil, nil, err
}
if err = ms.ApplyWorkflowExecutionContinuedAsNewEvent(
firstEventID,
continueAsNewEvent,
); err != nil {
return nil, nil, err
}
// TODO merge active & passive task generation
if err := ms.taskGenerator.GenerateWorkflowCloseTasks(
continueAsNewEvent.GetEventTime().AsTime(),
false,
); err != nil {
return nil, nil, err
}
return continueAsNewEvent, newMutableState, nil
}
func rolloverAutoResetPointsWithExpiringTime(
resetPoints *workflowpb.ResetPoints,
prevRunID string,
newExecutionStartTime time.Time,
namespaceRetention time.Duration,
) *workflowpb.ResetPoints {
if resetPoints.GetPoints() == nil {
return resetPoints
}
newPoints := make([]*workflowpb.ResetPointInfo, 0, len(resetPoints.Points))
// For continue-as-new, new execution start time is the same as previous execution close time,
// so reset points from the previous run will expire at new execution start time plus retention.
expireTime := newExecutionStartTime.Add(namespaceRetention)
for _, rp := range resetPoints.Points {
if rp.ExpireTime != nil && rp.ExpireTime.AsTime().Before(newExecutionStartTime) {
continue // run is expired, don't preserve it
}
if rp.GetRunId() == prevRunID {
rp.ExpireTime = timestamppb.New(expireTime)
}
newPoints = append(newPoints, rp)
}
return &workflowpb.ResetPoints{Points: newPoints}
}
func (ms *MutableStateImpl) ApplyWorkflowExecutionContinuedAsNewEvent(
firstEventID int64,
continueAsNewEvent *historypb.HistoryEvent,
) error {
if err := ms.UpdateWorkflowStateStatus(
enumsspb.WORKFLOW_EXECUTION_STATE_COMPLETED,
enumspb.WORKFLOW_EXECUTION_STATUS_CONTINUED_AS_NEW,
); err != nil {
return err
}
ms.executionInfo.CompletionEventBatchId = firstEventID // Used when completion event needs to be loaded from database
ms.executionInfo.NewExecutionRunId = continueAsNewEvent.GetWorkflowExecutionContinuedAsNewEventAttributes().GetNewExecutionRunId()
ms.executionInfo.CloseTime = continueAsNewEvent.GetEventTime()
ms.ClearStickyTaskQueue()
ms.writeEventToCache(continueAsNewEvent)
return nil
}
func (ms *MutableStateImpl) AddStartChildWorkflowExecutionInitiatedEvent(
workflowTaskCompletedEventID int64,
createRequestID string,
command *commandpb.StartChildWorkflowExecutionCommandAttributes,
targetNamespaceID namespace.ID,
) (*historypb.HistoryEvent, *persistencespb.ChildExecutionInfo, error) {
opTag := tag.WorkflowActionChildWorkflowInitiated
if err := ms.checkMutability(opTag); err != nil {
return nil, nil, err
}
event := ms.hBuilder.AddStartChildWorkflowExecutionInitiatedEvent(workflowTaskCompletedEventID, command, targetNamespaceID)
ci, err := ms.ApplyStartChildWorkflowExecutionInitiatedEvent(workflowTaskCompletedEventID, event, createRequestID)
if err != nil {
return nil, nil, err
}
// TODO merge active & passive task generation
if err := ms.taskGenerator.GenerateChildWorkflowTasks(
event,
); err != nil {
return nil, nil, err
}
return event, ci, nil
}
func (ms *MutableStateImpl) ApplyStartChildWorkflowExecutionInitiatedEvent(
firstEventID int64,
event *historypb.HistoryEvent,
createRequestID string,
) (*persistencespb.ChildExecutionInfo, error) {
initiatedEventID := event.GetEventId()
attributes := event.GetStartChildWorkflowExecutionInitiatedEventAttributes()
ci := &persistencespb.ChildExecutionInfo{
Version: event.GetVersion(),
InitiatedEventId: initiatedEventID,
InitiatedEventBatchId: firstEventID,
StartedEventId: common.EmptyEventID,
StartedWorkflowId: attributes.GetWorkflowId(),
CreateRequestId: createRequestID,
Namespace: attributes.GetNamespace(),
NamespaceId: attributes.GetNamespaceId(),
WorkflowTypeName: attributes.GetWorkflowType().GetName(),
ParentClosePolicy: attributes.GetParentClosePolicy(),
}
ms.pendingChildExecutionInfoIDs[ci.InitiatedEventId] = ci
ms.updateChildExecutionInfos[ci.InitiatedEventId] = ci
ms.approximateSize += ci.Size() + int64SizeBytes
ms.executionInfo.ChildExecutionCount++
ms.writeEventToCache(event)
return ci, nil
}
func (ms *MutableStateImpl) AddChildWorkflowExecutionStartedEvent(
execution *commonpb.WorkflowExecution,
workflowType *commonpb.WorkflowType,
initiatedID int64,
header *commonpb.Header,
clock *clockspb.VectorClock,
) (*historypb.HistoryEvent, error) {
opTag := tag.WorkflowActionChildWorkflowStarted
if err := ms.checkMutability(opTag); err != nil {
return nil, err
}
ci, ok := ms.GetChildExecutionInfo(initiatedID)
if !ok || ci.StartedEventId != common.EmptyEventID {
ms.logWarn(mutableStateInvalidHistoryActionMsg, opTag,
tag.WorkflowEventID(ms.GetNextEventID()),
tag.ErrorTypeInvalidHistoryAction,
tag.Bool(ok),
tag.WorkflowInitiatedID(initiatedID))
return nil, ms.createInternalServerError(opTag)
}
event := ms.hBuilder.AddChildWorkflowExecutionStartedEvent(
initiatedID,
namespace.Name(ci.GetNamespace()),
namespace.ID(ci.GetNamespaceId()),
execution,
workflowType,
header,
)
if err := ms.ApplyChildWorkflowExecutionStartedEvent(event, clock); err != nil {
return nil, err
}
return event, nil
}
func (ms *MutableStateImpl) ApplyChildWorkflowExecutionStartedEvent(
event *historypb.HistoryEvent,
clock *clockspb.VectorClock,
) error {
attributes := event.GetChildWorkflowExecutionStartedEventAttributes()
initiatedID := attributes.GetInitiatedEventId()
ci, ok := ms.GetChildExecutionInfo(initiatedID)
if !ok {
ms.logError(
fmt.Sprintf("unable to find child workflow event ID: %v in mutable state", initiatedID),
tag.ErrorTypeInvalidMutableStateAction,
)
return ErrMissingChildWorkflowInfo
}
ms.approximateSize -= ci.Size()
ci.StartedEventId = event.GetEventId()
ci.StartedRunId = attributes.GetWorkflowExecution().GetRunId()
ci.Clock = clock
ms.updateChildExecutionInfos[ci.InitiatedEventId] = ci
ms.approximateSize += ci.Size()
return nil
}
func (ms *MutableStateImpl) AddStartChildWorkflowExecutionFailedEvent(
initiatedID int64,
cause enumspb.StartChildWorkflowExecutionFailedCause,
initiatedEventAttributes *historypb.StartChildWorkflowExecutionInitiatedEventAttributes,
) (*historypb.HistoryEvent, error) {
opTag := tag.WorkflowActionChildWorkflowInitiationFailed
if err := ms.checkMutability(opTag); err != nil {
return nil, err
}
ci, ok := ms.GetChildExecutionInfo(initiatedID)
if !ok || ci.StartedEventId != common.EmptyEventID {
ms.logWarn(mutableStateInvalidHistoryActionMsg, opTag,
tag.WorkflowEventID(ms.GetNextEventID()),
tag.ErrorTypeInvalidHistoryAction,
tag.Bool(ok),
tag.WorkflowInitiatedID(initiatedID))
return nil, ms.createInternalServerError(opTag)
}
event := ms.hBuilder.AddStartChildWorkflowExecutionFailedEvent(
common.EmptyEventID, // TODO this field is not used at all
initiatedID,
cause,
namespace.Name(ci.Namespace),
namespace.ID(ci.NamespaceId),
initiatedEventAttributes.WorkflowId,
initiatedEventAttributes.WorkflowType,
initiatedEventAttributes.Control, // TODO this field is probably deprecated
)
if err := ms.ApplyStartChildWorkflowExecutionFailedEvent(event); err != nil {
return nil, err
}
return event, nil
}
func (ms *MutableStateImpl) ApplyStartChildWorkflowExecutionFailedEvent(
event *historypb.HistoryEvent,
) error {
attributes := event.GetStartChildWorkflowExecutionFailedEventAttributes()
initiatedID := attributes.GetInitiatedEventId()
return ms.DeletePendingChildExecution(initiatedID)
}
func (ms *MutableStateImpl) AddChildWorkflowExecutionCompletedEvent(
initiatedID int64,
childExecution *commonpb.WorkflowExecution,
attributes *historypb.WorkflowExecutionCompletedEventAttributes,
) (*historypb.HistoryEvent, error) {
opTag := tag.WorkflowActionChildWorkflowCompleted
if err := ms.checkMutability(opTag); err != nil {
return nil, err
}
ci, ok := ms.GetChildExecutionInfo(initiatedID)
if !ok || ci.StartedEventId == common.EmptyEventID {
ms.logWarn(mutableStateInvalidHistoryActionMsg, opTag,
tag.WorkflowEventID(ms.GetNextEventID()),
tag.ErrorTypeInvalidHistoryAction,
tag.Bool(ok),
tag.WorkflowInitiatedID(initiatedID))
return nil, ms.createInternalServerError(opTag)
}
workflowType := &commonpb.WorkflowType{
Name: ci.WorkflowTypeName,
}
event := ms.hBuilder.AddChildWorkflowExecutionCompletedEvent(
ci.InitiatedEventId,
ci.StartedEventId,
namespace.Name(ci.Namespace),
namespace.ID(ci.NamespaceId),
childExecution,
workflowType,
attributes.Result,
)
if err := ms.ApplyChildWorkflowExecutionCompletedEvent(event); err != nil {
return nil, err
}
return event, nil
}
func (ms *MutableStateImpl) ApplyChildWorkflowExecutionCompletedEvent(
event *historypb.HistoryEvent,
) error {
attributes := event.GetChildWorkflowExecutionCompletedEventAttributes()
initiatedID := attributes.GetInitiatedEventId()
return ms.DeletePendingChildExecution(initiatedID)
}
func (ms *MutableStateImpl) AddChildWorkflowExecutionFailedEvent(
initiatedID int64,
childExecution *commonpb.WorkflowExecution,
attributes *historypb.WorkflowExecutionFailedEventAttributes,
) (*historypb.HistoryEvent, error) {
opTag := tag.WorkflowActionChildWorkflowFailed
if err := ms.checkMutability(opTag); err != nil {
return nil, err
}
ci, ok := ms.GetChildExecutionInfo(initiatedID)
if !ok || ci.StartedEventId == common.EmptyEventID {
ms.logWarn(mutableStateInvalidHistoryActionMsg,
tag.WorkflowEventID(ms.GetNextEventID()),
tag.ErrorTypeInvalidHistoryAction,
tag.Bool(!ok),
tag.WorkflowInitiatedID(initiatedID))
return nil, ms.createInternalServerError(opTag)
}
workflowType := &commonpb.WorkflowType{
Name: ci.WorkflowTypeName,
}
event := ms.hBuilder.AddChildWorkflowExecutionFailedEvent(
ci.InitiatedEventId,
ci.StartedEventId,
namespace.Name(ci.Namespace),
namespace.ID(ci.NamespaceId),
childExecution,
workflowType,
attributes.Failure,
attributes.RetryState,
)
if err := ms.ApplyChildWorkflowExecutionFailedEvent(event); err != nil {
return nil, err
}
return event, nil
}
func (ms *MutableStateImpl) ApplyChildWorkflowExecutionFailedEvent(
event *historypb.HistoryEvent,
) error {
attributes := event.GetChildWorkflowExecutionFailedEventAttributes()
initiatedID := attributes.GetInitiatedEventId()
return ms.DeletePendingChildExecution(initiatedID)
}
func (ms *MutableStateImpl) AddChildWorkflowExecutionCanceledEvent(
initiatedID int64,
childExecution *commonpb.WorkflowExecution,
attributes *historypb.WorkflowExecutionCanceledEventAttributes,
) (*historypb.HistoryEvent, error) {
opTag := tag.WorkflowActionChildWorkflowCanceled
if err := ms.checkMutability(opTag); err != nil {
return nil, err
}
ci, ok := ms.GetChildExecutionInfo(initiatedID)
if !ok || ci.StartedEventId == common.EmptyEventID {
ms.logWarn(mutableStateInvalidHistoryActionMsg, opTag,
tag.WorkflowEventID(ms.GetNextEventID()),
tag.ErrorTypeInvalidHistoryAction,
tag.Bool(ok),
tag.WorkflowInitiatedID(initiatedID))
return nil, ms.createInternalServerError(opTag)
}
workflowType := &commonpb.WorkflowType{
Name: ci.WorkflowTypeName,
}
event := ms.hBuilder.AddChildWorkflowExecutionCanceledEvent(
ci.InitiatedEventId,
ci.StartedEventId,
namespace.Name(ci.Namespace),
namespace.ID(ci.NamespaceId),
childExecution,
workflowType,
attributes.Details,
)
if err := ms.ApplyChildWorkflowExecutionCanceledEvent(event); err != nil {
return nil, err
}
return event, nil
}
func (ms *MutableStateImpl) ApplyChildWorkflowExecutionCanceledEvent(
event *historypb.HistoryEvent,
) error {
attributes := event.GetChildWorkflowExecutionCanceledEventAttributes()
initiatedID := attributes.GetInitiatedEventId()
return ms.DeletePendingChildExecution(initiatedID)
}
func (ms *MutableStateImpl) AddChildWorkflowExecutionTerminatedEvent(
initiatedID int64,
childExecution *commonpb.WorkflowExecution,
_ *historypb.WorkflowExecutionTerminatedEventAttributes, // TODO this field is not used at all
) (*historypb.HistoryEvent, error) {
opTag := tag.WorkflowActionChildWorkflowTerminated
if err := ms.checkMutability(opTag); err != nil {
return nil, err
}
ci, ok := ms.GetChildExecutionInfo(initiatedID)
if !ok || ci.StartedEventId == common.EmptyEventID {
ms.logWarn(mutableStateInvalidHistoryActionMsg, opTag,
tag.WorkflowEventID(ms.GetNextEventID()),
tag.ErrorTypeInvalidHistoryAction,
tag.Bool(ok),
tag.WorkflowInitiatedID(initiatedID))
return nil, ms.createInternalServerError(opTag)
}
workflowType := &commonpb.WorkflowType{
Name: ci.WorkflowTypeName,
}
event := ms.hBuilder.AddChildWorkflowExecutionTerminatedEvent(
ci.InitiatedEventId,
ci.StartedEventId,
namespace.Name(ci.Namespace),
namespace.ID(ci.NamespaceId),
childExecution,
workflowType,
)
if err := ms.ApplyChildWorkflowExecutionTerminatedEvent(event); err != nil {
return nil, err
}
return event, nil
}
func (ms *MutableStateImpl) ApplyChildWorkflowExecutionTerminatedEvent(
event *historypb.HistoryEvent,
) error {
attributes := event.GetChildWorkflowExecutionTerminatedEventAttributes()
initiatedID := attributes.GetInitiatedEventId()
return ms.DeletePendingChildExecution(initiatedID)
}
func (ms *MutableStateImpl) AddChildWorkflowExecutionTimedOutEvent(
initiatedID int64,
childExecution *commonpb.WorkflowExecution,
attributes *historypb.WorkflowExecutionTimedOutEventAttributes,
) (*historypb.HistoryEvent, error) {
opTag := tag.WorkflowActionChildWorkflowTimedOut
if err := ms.checkMutability(opTag); err != nil {
return nil, err
}
ci, ok := ms.GetChildExecutionInfo(initiatedID)
if !ok || ci.StartedEventId == common.EmptyEventID {
ms.logWarn(mutableStateInvalidHistoryActionMsg, opTag,
tag.WorkflowEventID(ms.GetNextEventID()),
tag.ErrorTypeInvalidHistoryAction,
tag.Bool(ok),
tag.WorkflowInitiatedID(initiatedID))
return nil, ms.createInternalServerError(opTag)
}
workflowType := &commonpb.WorkflowType{
Name: ci.WorkflowTypeName,
}
event := ms.hBuilder.AddChildWorkflowExecutionTimedOutEvent(
ci.InitiatedEventId,
ci.StartedEventId,
namespace.Name(ci.Namespace),
namespace.ID(ci.NamespaceId),
childExecution,
workflowType,
attributes.RetryState,
)
if err := ms.ApplyChildWorkflowExecutionTimedOutEvent(event); err != nil {
return nil, err
}
return event, nil
}
func (ms *MutableStateImpl) ApplyChildWorkflowExecutionTimedOutEvent(
event *historypb.HistoryEvent,
) error {
attributes := event.GetChildWorkflowExecutionTimedOutEventAttributes()
initiatedID := attributes.GetInitiatedEventId()
return ms.DeletePendingChildExecution(initiatedID)
}
func (ms *MutableStateImpl) RetryActivity(
ai *persistencespb.ActivityInfo,
failure *failurepb.Failure,
) (enumspb.RetryState, error) {
opTag := tag.WorkflowActionActivityTaskRetry
if err := ms.checkMutability(opTag); err != nil {
return enumspb.RETRY_STATE_INTERNAL_SERVER_ERROR, err
}
activityVisitor := newActivityVisitor(ai, failure, ms.timeSource)
if state := activityVisitor.State(); state != enumspb.RETRY_STATE_IN_PROGRESS {
return state, nil
}
nextAttempt := ai.Attempt + 1
if err := ms.taskGenerator.GenerateActivityRetryTasks(ai.ScheduledEventId, activityVisitor.NextScheduledTime(), nextAttempt); err != nil {
return enumspb.RETRY_STATE_INTERNAL_SERVER_ERROR, err
}
// we need to store activity info size since pendingActivityInfoIDs holds pointers to activity
// info and if prev found it points to the same activity info as ai, so updating ai will cause
// size of prev change.
var originalSize int
if prev, ok := ms.pendingActivityInfoIDs[ai.ScheduledEventId]; ok {
originalSize = prev.Size()
}
ai = activityVisitor.UpdateActivityInfo(
ai,
ms.GetCurrentVersion(),
nextAttempt,
ms.truncateRetryableActivityFailure(failure),
)
ms.approximateSize += ai.Size() - originalSize
ms.updateActivityInfos[ai.ScheduledEventId] = ai
ms.syncActivityTasks[ai.ScheduledEventId] = struct{}{}
return enumspb.RETRY_STATE_IN_PROGRESS, nil
}
func (ms *MutableStateImpl) truncateRetryableActivityFailure(
activityFailure *failurepb.Failure,
) *failurepb.Failure {
namespaceName := ms.namespaceEntry.Name().String()
failureSize := activityFailure.Size()
if failureSize <= ms.config.MutableStateActivityFailureSizeLimitWarn(namespaceName) {
return activityFailure
}
throttledLogger := log.With(
ms.shard.GetThrottledLogger(),
tag.WorkflowNamespace(namespaceName),
tag.WorkflowID(ms.executionInfo.WorkflowId),
tag.WorkflowRunID(ms.executionState.RunId),
tag.BlobSize(int64(failureSize)),
tag.BlobSizeViolationOperation("RetryActivity"),
)
sizeLimitError := ms.config.MutableStateActivityFailureSizeLimitError(namespaceName)
if failureSize <= sizeLimitError {
throttledLogger.Warn("Activity failure size exceeds warning limit for mutable state.")
return activityFailure
}
throttledLogger.Warn("Activity failure size exceeds error limit for mutable state, truncated.")
// nonRetryable is set to false here as only retryable failures are recorded in mutable state.
// also when this method is called, the check for isRetryable is already done, so the value
// is only for visibility/debugging purpose.
serverFailure := failure.NewServerFailure(common.FailureReasonFailureExceedsLimit, false)
serverFailure.Cause = failure.Truncate(activityFailure, sizeLimitError)
return serverFailure
}
func (ms *MutableStateImpl) GetHistorySize() int64 {
return ms.executionInfo.ExecutionStats.HistorySize
}
func (ms *MutableStateImpl) AddHistorySize(size int64) {
ms.executionInfo.ExecutionStats.HistorySize += size
}
// processCloseCallbacks triggers "WorkflowClosed" callbacks, applying the state machine transition that schedules
// callback tasks.
func (ms *MutableStateImpl) processCloseCallbacks() error {
continuedAsNew := ms.GetExecutionInfo().NewExecutionRunId != ""
if continuedAsNew {
return nil
}
coll := callbacks.MachineCollection(ms.HSM())
for _, node := range coll.List() {
cb, err := coll.Data(node.Key.ID)
if err != nil {
return err
}
// Only try to trigger "WorkflowClosed" callbacks.
if _, ok := cb.PublicInfo.Trigger.Variant.(*workflowpb.CallbackInfo_Trigger_WorkflowClosed); !ok {
continue
}
err = coll.Transition(node.Key.ID, func(cb callbacks.Callback) (hsm.TransitionOutput, error) {
return callbacks.TransitionScheduled.Apply(cb, callbacks.EventScheduled{})
})
if err != nil {
return err
}
}
return nil
}
// TODO mutable state should generate corresponding transfer / timer tasks according to
// updates accumulated, while currently all transfer / timer tasks are managed manually
func (ms *MutableStateImpl) AddTasks(
newTasks ...tasks.Task,
) {
now := ms.timeSource.Now()
for _, task := range newTasks {
category := task.GetCategory()
if category.Type() == tasks.CategoryTypeScheduled &&
task.GetVisibilityTime().Sub(now) > maxScheduledTaskDuration {
ms.logger.Info("Dropped long duration scheduled task.", tasks.Tags(task)...)
continue
}
ms.InsertTasks[category] = append(ms.InsertTasks[category], task)
}
}
func (ms *MutableStateImpl) PopTasks() map[tasks.Category][]tasks.Task {
insterTasks := ms.InsertTasks
ms.InsertTasks = make(map[tasks.Category][]tasks.Task)
return insterTasks
}
func (ms *MutableStateImpl) SetUpdateCondition(
nextEventIDInDB int64,
dbRecordVersion int64,
) {
ms.nextEventIDInDB = nextEventIDInDB
ms.dbRecordVersion = dbRecordVersion
}
func (ms *MutableStateImpl) GetUpdateCondition() (int64, int64) {
return ms.nextEventIDInDB, ms.dbRecordVersion
}
func (ms *MutableStateImpl) SetSpeculativeWorkflowTaskTimeoutTask(
task *tasks.WorkflowTaskTimeoutTask,
) error {
ms.speculativeWorkflowTaskTimeoutTask = task
return ms.shard.AddSpeculativeWorkflowTaskTimeoutTask(task)
}
func (ms *MutableStateImpl) CheckSpeculativeWorkflowTaskTimeoutTask(
task *tasks.WorkflowTaskTimeoutTask,
) bool {
return ms.speculativeWorkflowTaskTimeoutTask == task
}
func (ms *MutableStateImpl) RemoveSpeculativeWorkflowTaskTimeoutTask() {
if ms.speculativeWorkflowTaskTimeoutTask != nil {
// Cancelling task prevents it from being submitted to scheduler in memoryScheduledQueue.
ms.speculativeWorkflowTaskTimeoutTask.Cancel()
ms.speculativeWorkflowTaskTimeoutTask = nil
}
}
func (ms *MutableStateImpl) GetWorkflowStateStatus() (enumsspb.WorkflowExecutionState, enumspb.WorkflowExecutionStatus) {
return ms.executionState.State, ms.executionState.Status
}
func (ms *MutableStateImpl) UpdateWorkflowStateStatus(
state enumsspb.WorkflowExecutionState,
status enumspb.WorkflowExecutionStatus,
) error {
return setStateStatus(ms.executionState, state, status)
}
func (ms *MutableStateImpl) IsDirty() bool {
return ms.hBuilder.IsDirty() || len(ms.InsertTasks) > 0 || (ms.stateMachineNode != nil && ms.stateMachineNode.Dirty())
}
func (ms *MutableStateImpl) StartTransaction(
namespaceEntry *namespace.Namespace,
) (bool, error) {
if ms.IsDirty() {
ms.logger.Error("MutableState encountered dirty transaction",
tag.WorkflowNamespaceID(ms.executionInfo.NamespaceId),
tag.WorkflowID(ms.executionInfo.WorkflowId),
tag.WorkflowRunID(ms.executionState.RunId),
tag.Value(ms.hBuilder),
)
metrics.MutableStateChecksumInvalidated.With(ms.metricsHandler).Record(1)
return false, serviceerror.NewUnavailable("MutableState encountered dirty transaction")
}
namespaceEntry, err := ms.startTransactionHandleNamespaceMigration(namespaceEntry)
if err != nil {
return false, err
}
ms.namespaceEntry = namespaceEntry
ms.currentTransactionNamespaceFailoverVersion = namespaceEntry.FailoverVersion()
if err := ms.UpdateCurrentVersion(ms.currentTransactionNamespaceFailoverVersion, false); err != nil {
return false, err
}
flushBeforeReady, err := ms.startTransactionHandleWorkflowTaskFailover()
if err != nil {
return false, err
}
return flushBeforeReady, nil
}
func (ms *MutableStateImpl) CloseTransactionAsMutation(
transactionPolicy TransactionPolicy,
) (*persistence.WorkflowMutation, []*persistence.WorkflowEvents, error) {
if err := ms.prepareCloseTransaction(
transactionPolicy,
); err != nil {
return nil, nil, err
}
// It is important to convert speculative WT to normal before prepareEventsAndReplicationTasks,
// because prepareEventsAndReplicationTasks will move internal buffered events to the history,
// and WT related events (WTScheduled, in particular) need to go first.
if err := ms.workflowTaskManager.convertSpeculativeWorkflowTaskToNormal(); err != nil {
return nil, nil, err
}
workflowEventsSeq, bufferEvents, clearBuffer, err := ms.prepareEventsAndReplicationTasks(transactionPolicy)
if err != nil {
return nil, nil, err
}
if len(workflowEventsSeq) > 0 {
lastEvents := workflowEventsSeq[len(workflowEventsSeq)-1].Events
lastEvent := lastEvents[len(lastEvents)-1]
if err := ms.updateWithLastWriteEvent(
lastEvent,
transactionPolicy,
); err != nil {
return nil, nil, err
}
}
// update last update time
ms.executionInfo.LastUpdateTime = timestamppb.New(ms.shard.GetTimeSource().Now())
// We generate checksum here based on the assumption that the returned
// snapshot object is considered immutable. As of this writing, the only
// code that modifies the returned object lives inside Context.resetWorkflowExecution.
// Currently, the updates done inside Context.resetWorkflowExecution don't
// impact the checksum calculation.
checksum := ms.generateChecksum()
if ms.dbRecordVersion == 0 {
// noop, existing behavior
} else {
ms.dbRecordVersion += 1
}
workflowMutation := &persistence.WorkflowMutation{
ExecutionInfo: ms.executionInfo,
ExecutionState: ms.executionState,
NextEventID: ms.hBuilder.NextEventID(),
UpsertActivityInfos: ms.updateActivityInfos,
DeleteActivityInfos: ms.deleteActivityInfos,
UpsertTimerInfos: ms.updateTimerInfos,
DeleteTimerInfos: ms.deleteTimerInfos,
UpsertChildExecutionInfos: ms.updateChildExecutionInfos,
DeleteChildExecutionInfos: ms.deleteChildExecutionInfos,
UpsertRequestCancelInfos: ms.updateRequestCancelInfos,
DeleteRequestCancelInfos: ms.deleteRequestCancelInfos,
UpsertSignalInfos: ms.updateSignalInfos,
DeleteSignalInfos: ms.deleteSignalInfos,
UpsertSignalRequestedIDs: ms.updateSignalRequestedIDs,
DeleteSignalRequestedIDs: ms.deleteSignalRequestedIDs,
NewBufferedEvents: bufferEvents,
ClearBufferedEvents: clearBuffer,
Tasks: ms.InsertTasks,
Condition: ms.nextEventIDInDB,
DBRecordVersion: ms.dbRecordVersion,
Checksum: checksum,
}
ms.checksum = checksum
if err := ms.cleanupTransaction(transactionPolicy); err != nil {
return nil, nil, err
}
return workflowMutation, workflowEventsSeq, nil
}
func (ms *MutableStateImpl) CloseTransactionAsSnapshot(
transactionPolicy TransactionPolicy,
) (*persistence.WorkflowSnapshot, []*persistence.WorkflowEvents, error) {
if err := ms.prepareCloseTransaction(
transactionPolicy,
); err != nil {
return nil, nil, err
}
if err := ms.workflowTaskManager.convertSpeculativeWorkflowTaskToNormal(); err != nil {
return nil, nil, err
}
workflowEventsSeq, bufferEvents, _, err := ms.prepareEventsAndReplicationTasks(transactionPolicy)
if err != nil {
return nil, nil, err
}
if len(bufferEvents) > 0 {
// TODO do we need the functionality to generate snapshot with buffered events?
return nil, nil, serviceerror.NewInternal("cannot generate workflow snapshot with buffered events")
}
if len(workflowEventsSeq) > 0 {
lastEvents := workflowEventsSeq[len(workflowEventsSeq)-1].Events
lastEvent := lastEvents[len(lastEvents)-1]
if err := ms.updateWithLastWriteEvent(
lastEvent,
transactionPolicy,
); err != nil {
return nil, nil, err
}
}
// update last update time
ms.executionInfo.LastUpdateTime = timestamppb.New(ms.shard.GetTimeSource().Now())
// We generate checksum here based on the assumption that the returned
// snapshot object is considered immutable. As of this writing, the only
// code that modifies the returned object lives inside Context.resetWorkflowExecution.
// Currently, the updates done inside Context.resetWorkflowExecution don't
// impact the checksum calculation.
checksum := ms.generateChecksum()
if ms.dbRecordVersion == 0 {
// noop, existing behavior
} else {
ms.dbRecordVersion += 1
}
workflowSnapshot := &persistence.WorkflowSnapshot{
ExecutionInfo: ms.executionInfo,
ExecutionState: ms.executionState,
NextEventID: ms.hBuilder.NextEventID(),
ActivityInfos: ms.pendingActivityInfoIDs,
TimerInfos: ms.pendingTimerInfoIDs,
ChildExecutionInfos: ms.pendingChildExecutionInfoIDs,
RequestCancelInfos: ms.pendingRequestCancelInfoIDs,
SignalInfos: ms.pendingSignalInfoIDs,
SignalRequestedIDs: ms.pendingSignalRequestedIDs,
Tasks: ms.InsertTasks,
Condition: ms.nextEventIDInDB,
DBRecordVersion: ms.dbRecordVersion,
Checksum: checksum,
}
ms.checksum = checksum
if err := ms.cleanupTransaction(transactionPolicy); err != nil {
return nil, nil, err
}
return workflowSnapshot, workflowEventsSeq, nil
}
func (ms *MutableStateImpl) IsResourceDuplicated(
resourceDedupKey definition.DeduplicationID,
) bool {
id := definition.GenerateDeduplicationKey(resourceDedupKey)
_, duplicated := ms.appliedEvents[id]
return duplicated
}
func (ms *MutableStateImpl) UpdateDuplicatedResource(
resourceDedupKey definition.DeduplicationID,
) {
id := definition.GenerateDeduplicationKey(resourceDedupKey)
ms.appliedEvents[id] = struct{}{}
}
func (ms *MutableStateImpl) GenerateMigrationTasks() ([]tasks.Task, int64, error) {
return ms.taskGenerator.GenerateMigrationTasks()
}
func (ms *MutableStateImpl) prepareCloseTransaction(
transactionPolicy TransactionPolicy,
) error {
if err := ms.closeTransactionWithPolicyCheck(
transactionPolicy,
); err != nil {
return err
}
if err := ms.closeTransactionHandleBufferedEventsLimit(
transactionPolicy,
); err != nil {
return err
}
if err := ms.closeTransactionHandleWorkflowReset(
transactionPolicy,
); err != nil {
return err
}
ms.executionInfo.StateTransitionCount += 1
if ms.config.EnableMutableStateTransitionHistory() {
ms.executionInfo.TransitionHistory = UpdatedTransitionHistory(
ms.executionInfo.TransitionHistory,
ms.currentTransactionNamespaceFailoverVersion,
ms.executionInfo.StateTransitionCount,
)
}
// TODO(bergundy): Collapse timer tasks.
if err := ms.taskGenerator.GenerateDirtySubStateMachineTasks(ms.shard.StateMachineRegistry()); err != nil {
return err
}
for _, t := range ms.currentTransactionAddedStateMachineEventTypes {
def, ok := ms.shard.StateMachineRegistry().EventDefinition(t)
if !ok {
return serviceerror.NewInternal(fmt.Sprintf("no event definition registered for %v", t))
}
if def.IsWorkflowTaskTrigger() {
if !ms.HasPendingWorkflowTask() {
if _, err := ms.AddWorkflowTaskScheduledEvent(
false,
enumsspb.WORKFLOW_TASK_TYPE_NORMAL,
); err != nil {
return err
}
}
break
}
}
ms.closeTransactionCollapseVisibilityTasks()
// TODO merge active & passive task generation
// NOTE: this function must be the last call
// since we only generate at most one activity & user timer,
// regardless of how many activity & user timer created
// so the calculation must be at the very end
return ms.closeTransactionHandleActivityUserTimerTasks(transactionPolicy)
}
func (ms *MutableStateImpl) cleanupTransaction(
_ TransactionPolicy,
) error {
ms.updateActivityInfos = make(map[int64]*persistencespb.ActivityInfo)
ms.deleteActivityInfos = make(map[int64]struct{})
ms.syncActivityTasks = make(map[int64]struct{})
ms.updateTimerInfos = make(map[string]*persistencespb.TimerInfo)
ms.deleteTimerInfos = make(map[string]struct{})
ms.updateChildExecutionInfos = make(map[int64]*persistencespb.ChildExecutionInfo)
ms.deleteChildExecutionInfos = make(map[int64]struct{})
ms.updateRequestCancelInfos = make(map[int64]*persistencespb.RequestCancelInfo)
ms.deleteRequestCancelInfos = make(map[int64]struct{})
ms.updateSignalInfos = make(map[int64]*persistencespb.SignalInfo)
ms.deleteSignalInfos = make(map[int64]struct{})
ms.updateSignalRequestedIDs = make(map[string]struct{})
ms.deleteSignalRequestedIDs = make(map[string]struct{})
ms.stateInDB = ms.executionState.State
ms.nextEventIDInDB = ms.GetNextEventID()
// ms.dbRecordVersion remains the same
ms.hBuilder = historybuilder.New(
ms.timeSource,
ms.shard.GenerateTaskIDs,
ms.GetCurrentVersion(),
ms.nextEventIDInDB,
ms.bufferEventsInDB,
ms.metricsHandler,
)
ms.InsertTasks = make(map[tasks.Category][]tasks.Task)
// Clear outputs for the next transaction.
ms.stateMachineNode.ClearTransactionState()
// Clear out transient state machine state.
ms.currentTransactionAddedStateMachineEventTypes = nil
return nil
}
func (ms *MutableStateImpl) prepareEventsAndReplicationTasks(
transactionPolicy TransactionPolicy,
) ([]*persistence.WorkflowEvents, []*historypb.HistoryEvent, bool, error) {
currentBranchToken, err := ms.GetCurrentBranchToken()
if err != nil {
return nil, nil, false, err
}
historyMutation, err := ms.hBuilder.Finish(!ms.HasStartedWorkflowTask())
if err != nil {
return nil, nil, false, err
}
// TODO @wxing1292 need more refactoring to make the logic clean
ms.bufferEventsInDB = historyMutation.MemBufferBatch
newBufferBatch := historyMutation.DBBufferBatch
clearBuffer := historyMutation.DBClearBuffer
newEventsBatches := historyMutation.DBEventsBatches
ms.updatePendingEventIDs(historyMutation.ScheduledIDToStartedID)
workflowEventsSeq := make([]*persistence.WorkflowEvents, len(newEventsBatches))
historyNodeTxnIDs, err := ms.shard.GenerateTaskIDs(len(newEventsBatches))
if err != nil {
return nil, nil, false, err
}
for index, eventBatch := range newEventsBatches {
workflowEventsSeq[index] = &persistence.WorkflowEvents{
NamespaceID: ms.executionInfo.NamespaceId,
WorkflowID: ms.executionInfo.WorkflowId,
RunID: ms.executionState.RunId,
BranchToken: currentBranchToken,
PrevTxnID: ms.executionInfo.LastFirstEventTxnId,
TxnID: historyNodeTxnIDs[index],
Events: eventBatch,
}
ms.executionInfo.LastFirstEventId = eventBatch[0].GetEventId()
ms.executionInfo.LastFirstEventTxnId = historyNodeTxnIDs[index]
}
if err := ms.validateNoEventsAfterWorkflowFinish(
transactionPolicy,
workflowEventsSeq,
); err != nil {
return nil, nil, false, err
}
for _, workflowEvents := range workflowEventsSeq {
if err := ms.eventsToReplicationTask(transactionPolicy, workflowEvents.Events); err != nil {
return nil, nil, false, err
}
}
ms.InsertTasks[tasks.CategoryReplication] = append(
ms.InsertTasks[tasks.CategoryReplication],
ms.syncActivityToReplicationTask(transactionPolicy)...,
)
if transactionPolicy == TransactionPolicyPassive &&
len(ms.InsertTasks[tasks.CategoryReplication]) > 0 {
return nil, nil, false, serviceerror.NewInternal("should not generate replication task when close transaction as passive")
}
return workflowEventsSeq, newBufferBatch, clearBuffer, nil
}
func (ms *MutableStateImpl) eventsToReplicationTask(
transactionPolicy TransactionPolicy,
events []*historypb.HistoryEvent,
) error {
switch transactionPolicy {
case TransactionPolicyActive:
if ms.generateReplicationTask() {
return ms.taskGenerator.GenerateHistoryReplicationTasks(events)
}
return nil
case TransactionPolicyPassive:
return nil
default:
panic(fmt.Sprintf("unknown transaction policy: %v", transactionPolicy))
}
}
func (ms *MutableStateImpl) syncActivityToReplicationTask(
transactionPolicy TransactionPolicy,
) []tasks.Task {
now := time.Now().UTC()
switch transactionPolicy {
case TransactionPolicyActive:
if ms.generateReplicationTask() {
return convertSyncActivityInfos(
now,
definition.NewWorkflowKey(
ms.executionInfo.NamespaceId,
ms.executionInfo.WorkflowId,
ms.executionState.RunId,
),
ms.pendingActivityInfoIDs,
ms.syncActivityTasks,
)
}
return nil
case TransactionPolicyPassive:
return emptyTasks
default:
panic(fmt.Sprintf("unknown transaction policy: %v", transactionPolicy))
}
}
func (ms *MutableStateImpl) updatePendingEventIDs(
scheduledIDToStartedID map[int64]int64,
) {
for scheduledEventID, startedEventID := range scheduledIDToStartedID {
if activityInfo, ok := ms.GetActivityInfo(scheduledEventID); ok {
activityInfo.StartedEventId = startedEventID
ms.updateActivityInfos[activityInfo.ScheduledEventId] = activityInfo
continue
}
if childInfo, ok := ms.GetChildExecutionInfo(scheduledEventID); ok {
childInfo.StartedEventId = startedEventID
ms.updateChildExecutionInfos[childInfo.InitiatedEventId] = childInfo
continue
}
}
}
func (ms *MutableStateImpl) updateWithLastWriteEvent(
lastEvent *historypb.HistoryEvent,
transactionPolicy TransactionPolicy,
) error {
if transactionPolicy == TransactionPolicyPassive {
// already handled in mutable state.
return nil
}
currentVersionHistory, err := versionhistory.GetCurrentVersionHistory(ms.executionInfo.VersionHistories)
if err != nil {
return err
}
if err := versionhistory.AddOrUpdateVersionHistoryItem(currentVersionHistory, versionhistory.NewVersionHistoryItem(
lastEvent.GetEventId(), lastEvent.GetVersion(),
)); err != nil {
return err
}
ms.executionInfo.LastEventTaskId = lastEvent.GetTaskId()
return nil
}
// validateNoEventsAfterWorkflowFinish perform check on history event batch
// NOTE: do not apply this check on every batch, since transient
// workflow task && workflow finish will be broken (the first batch)
func (ms *MutableStateImpl) validateNoEventsAfterWorkflowFinish(
transactionPolicy TransactionPolicy,
workflowEventSeq []*persistence.WorkflowEvents,
) error {
if transactionPolicy == TransactionPolicyPassive ||
len(workflowEventSeq) == 0 {
return nil
}
// only do check if workflow is finished
if ms.executionState.State != enumsspb.WORKFLOW_EXECUTION_STATE_COMPLETED {
return nil
}
// workflow close
// this will perform check on the last event of last batch
// NOTE: do not apply this check on every batch, since transient
// workflow task && workflow finish will be broken (the first batch)
eventBatch := workflowEventSeq[len(workflowEventSeq)-1].Events
lastEvent := eventBatch[len(eventBatch)-1]
switch lastEvent.GetEventType() {
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_COMPLETED,
enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_FAILED,
enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_TIMED_OUT,
enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_TERMINATED,
enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_CONTINUED_AS_NEW,
enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_CANCELED:
return nil
default:
ms.logError(
"encountered case where events appears after workflow finish.",
tag.WorkflowNamespaceID(ms.executionInfo.NamespaceId),
tag.WorkflowID(ms.executionInfo.WorkflowId),
tag.WorkflowRunID(ms.executionState.RunId),
)
return consts.ErrEventsAterWorkflowFinish
}
}
func (ms *MutableStateImpl) startTransactionHandleNamespaceMigration(
namespaceEntry *namespace.Namespace,
) (*namespace.Namespace, error) {
// NOTE:
// the main idea here is to guarantee that buffered events & namespace migration works
// e.g. handle buffered events during version 0 => version > 0 by postponing namespace migration
// * flush buffered events as if namespace is still local
// * use updated namespace for actual call
lastWriteVersion, err := ms.GetLastWriteVersion()
if err != nil {
return nil, err
}
// local namespace -> global namespace && with started workflow task
if lastWriteVersion == common.EmptyVersion && namespaceEntry.FailoverVersion() > common.EmptyVersion && ms.HasStartedWorkflowTask() {
localNamespaceMutation := namespace.NewPretendAsLocalNamespace(
ms.clusterMetadata.GetCurrentClusterName(),
)
return namespaceEntry.Clone(localNamespaceMutation), nil
}
return namespaceEntry, nil
}
func (ms *MutableStateImpl) startTransactionHandleWorkflowTaskFailover() (bool, error) {
if !ms.IsWorkflowExecutionRunning() {
return false, nil
}
// NOTE:
// the main idea here is to guarantee that once there is a workflow task started
// all events ending in the buffer should have the same version
// Handling mutable state turn from standby to active, while having a workflow task on the fly
workflowTask := ms.GetStartedWorkflowTask()
if workflowTask == nil || workflowTask.Version >= ms.GetCurrentVersion() {
// no pending workflow tasks, no buffered events
// or workflow task has higher / equal version
return false, nil
}
currentVersion := ms.GetCurrentVersion()
lastWriteVersion, err := ms.GetLastWriteVersion()
if err != nil {
return false, err
}
if lastWriteVersion != workflowTask.Version {
return false, serviceerror.NewInternal(fmt.Sprintf("MutableStateImpl encountered mismatch version, workflow task: %v, last write version %v", workflowTask.Version, lastWriteVersion))
}
lastWriteSourceCluster := ms.clusterMetadata.ClusterNameForFailoverVersion(ms.namespaceEntry.IsGlobalNamespace(), lastWriteVersion)
currentVersionCluster := ms.clusterMetadata.ClusterNameForFailoverVersion(ms.namespaceEntry.IsGlobalNamespace(), currentVersion)
currentCluster := ms.clusterMetadata.GetCurrentClusterName()
// there are 4 cases for version changes (based on version from namespace cache)
// NOTE: namespace cache version change may occur after seeing events with higher version
// meaning that the flush buffer logic in NDC branch manager should be kept.
//
// 1. active -> passive => fail workflow task & flush buffer using last write version
// 2. active -> active => fail workflow task & flush buffer using last write version
// 3. passive -> active => fail workflow task using current version, no buffered events
// 4. passive -> passive => no buffered events, since always passive, nothing to be done
// handle case 4
if lastWriteSourceCluster != currentCluster && currentVersionCluster != currentCluster {
// do a sanity check on buffered events
if ms.HasBufferedEvents() {
return false, serviceerror.NewInternal("MutableStateImpl encountered previous passive workflow with buffered events")
}
return false, nil
}
// handle case 1 & 2
var flushBufferVersion = lastWriteVersion
// handle case 3
if lastWriteSourceCluster != currentCluster && currentVersionCluster == currentCluster {
// do a sanity check on buffered events
if ms.HasBufferedEvents() {
return false, serviceerror.NewInternal("MutableStateImpl encountered previous passive workflow with buffered events")
}
flushBufferVersion = currentVersion
}
// this workflow was previous active (whether it has buffered events or not),
// the in flight workflow task must be failed to guarantee all events within same
// event batch shard the same version
if err := ms.UpdateCurrentVersion(flushBufferVersion, true); err != nil {
return false, err
}
// we have a workflow task with buffered events on the fly with a lower version, fail it
if _, err := failWorkflowTask(
ms,
workflowTask,
enumspb.WORKFLOW_TASK_FAILED_CAUSE_FAILOVER_CLOSE_COMMAND,
); err != nil {
return false, err
}
err = ScheduleWorkflowTask(ms)
if err != nil {
return false, err
}
return true, nil
}
func (ms *MutableStateImpl) closeTransactionWithPolicyCheck(
transactionPolicy TransactionPolicy,
) error {
switch transactionPolicy {
case TransactionPolicyActive:
// Cannot use ms.namespaceEntry.ActiveClusterName() because currentVersion may be updated during this transaction in
// passive cluster. For example: if passive cluster sees conflict and decided to terminate this workflow. The
// currentVersion on mutable state would be updated to point to last write version which is current (passive) cluster.
activeCluster := ms.clusterMetadata.ClusterNameForFailoverVersion(ms.namespaceEntry.IsGlobalNamespace(), ms.GetCurrentVersion())
currentCluster := ms.clusterMetadata.GetCurrentClusterName()
if activeCluster != currentCluster {
namespaceID := ms.GetExecutionInfo().NamespaceId
return serviceerror.NewNamespaceNotActive(namespaceID, currentCluster, activeCluster)
}
return nil
case TransactionPolicyPassive:
return nil
default:
panic(fmt.Sprintf("unknown transaction policy: %v", transactionPolicy))
}
}
func (ms *MutableStateImpl) BufferSizeAcceptable() bool {
if ms.hBuilder.NumBufferedEvents() > ms.config.MaximumBufferedEventsBatch() {
return false
}
if ms.hBuilder.SizeInBytesOfBufferedEvents() > ms.config.MaximumBufferedEventsSizeInBytes() {
return false
}
return true
}
func (ms *MutableStateImpl) closeTransactionHandleBufferedEventsLimit(
transactionPolicy TransactionPolicy,
) error {
if transactionPolicy == TransactionPolicyPassive ||
!ms.IsWorkflowExecutionRunning() {
return nil
}
if ms.BufferSizeAcceptable() {
return nil
}
// Handling buffered events size issue
if workflowTask := ms.GetStartedWorkflowTask(); workflowTask != nil {
// we have a workflow task on the fly with a lower version, fail it
if _, err := failWorkflowTask(
ms,
workflowTask,
enumspb.WORKFLOW_TASK_FAILED_CAUSE_FORCE_CLOSE_COMMAND,
); err != nil {
return err
}
err := ScheduleWorkflowTask(ms)
if err != nil {
return err
}
}
return nil
}
func (ms *MutableStateImpl) closeTransactionHandleWorkflowReset(
transactionPolicy TransactionPolicy,
) error {
if transactionPolicy == TransactionPolicyPassive ||
!ms.IsWorkflowExecutionRunning() {
return nil
}
// compare with bad client binary checksum and schedule a reset task
// only schedule reset task if current doesn't have childWFs.
// TODO: This will be removed once our reset allows childWFs
if len(ms.GetPendingChildExecutionInfos()) != 0 {
return nil
}
namespaceEntry, err := ms.shard.GetNamespaceRegistry().GetNamespaceByID(namespace.ID(ms.executionInfo.NamespaceId))
if err != nil {
return err
}
if _, pt := FindAutoResetPoint(
ms.timeSource,
namespaceEntry.VerifyBinaryChecksum,
ms.GetExecutionInfo().AutoResetPoints,
); pt != nil {
if err := ms.taskGenerator.GenerateWorkflowResetTasks(); err != nil {
return err
}
ms.logInfo("Auto-Reset task is scheduled",
tag.WorkflowNamespace(namespaceEntry.Name().String()),
tag.WorkflowID(ms.executionInfo.WorkflowId),
tag.WorkflowRunID(ms.executionState.RunId),
tag.WorkflowResetBaseRunID(pt.GetRunId()),
tag.WorkflowEventID(pt.GetFirstWorkflowTaskCompletedId()),
tag.WorkflowBinaryChecksum(pt.GetBinaryChecksum()),
)
}
return nil
}
func (ms *MutableStateImpl) closeTransactionHandleActivityUserTimerTasks(
transactionPolicy TransactionPolicy,
) error {
switch transactionPolicy {
case TransactionPolicyActive:
if !ms.IsWorkflowExecutionRunning() {
return nil
}
if err := ms.taskGenerator.GenerateActivityTimerTasks(); err != nil {
return err
}
return ms.taskGenerator.GenerateUserTimerTasks()
case TransactionPolicyPassive:
return nil
default:
panic(fmt.Sprintf("unknown transaction policy: %v", transactionPolicy))
}
}
// Visibility tasks are collapsed into a single one: START < UPSERT < CLOSE < DELETE
// Their enum values are already in order, so using them to make the code simpler.
// Any other task type is preserved in order.
// Eg: [START, UPSERT, TP1, CLOSE, TP2, TP3] -> [TP1, CLOSE, TP2, TP3]
func (ms *MutableStateImpl) closeTransactionCollapseVisibilityTasks() {
visTasks := ms.InsertTasks[tasks.CategoryVisibility]
if len(visTasks) < 2 {
return
}
var visTaskToKeep tasks.Task
lastIndex := -1
for i, task := range visTasks {
switch task.GetType() {
case enumsspb.TASK_TYPE_VISIBILITY_START_EXECUTION,
enumsspb.TASK_TYPE_VISIBILITY_UPSERT_EXECUTION,
enumsspb.TASK_TYPE_VISIBILITY_CLOSE_EXECUTION,
enumsspb.TASK_TYPE_VISIBILITY_DELETE_EXECUTION:
if visTaskToKeep == nil || task.GetType() >= visTaskToKeep.GetType() {
visTaskToKeep = task
}
lastIndex = i
}
}
if visTaskToKeep == nil {
return
}
collapsedVisTasks := make([]tasks.Task, 0, len(visTasks))
for i, task := range visTasks {
switch task.GetType() {
case enumsspb.TASK_TYPE_VISIBILITY_START_EXECUTION,
enumsspb.TASK_TYPE_VISIBILITY_UPSERT_EXECUTION,
enumsspb.TASK_TYPE_VISIBILITY_CLOSE_EXECUTION,
enumsspb.TASK_TYPE_VISIBILITY_DELETE_EXECUTION:
if i == lastIndex {
collapsedVisTasks = append(collapsedVisTasks, visTaskToKeep)
}
default:
collapsedVisTasks = append(collapsedVisTasks, task)
}
}
ms.InsertTasks[tasks.CategoryVisibility] = collapsedVisTasks
}
func (ms *MutableStateImpl) generateReplicationTask() bool {
return len(ms.namespaceEntry.ClusterNames()) > 1
}
func (ms *MutableStateImpl) checkMutability(
actionTag tag.ZapTag,
) error {
if !ms.IsWorkflowExecutionRunning() {
ms.logWarn(
mutableStateInvalidHistoryActionMsg,
tag.WorkflowEventID(ms.GetNextEventID()),
tag.ErrorTypeInvalidHistoryAction,
tag.WorkflowState(ms.executionState.State),
actionTag,
)
return ErrWorkflowFinished
}
return nil
}
func (ms *MutableStateImpl) generateChecksum() *persistencespb.Checksum {
if !ms.shouldGenerateChecksum() {
return nil
}
csum, err := generateMutableStateChecksum(ms)
if err != nil {
ms.logWarn("error generating MutableState checksum", tag.Error(err))
return nil
}
return csum
}
func (ms *MutableStateImpl) shouldGenerateChecksum() bool {
if ms.namespaceEntry == nil {
return false
}
return rand.Intn(100) < ms.config.MutableStateChecksumGenProbability(ms.namespaceEntry.Name().String())
}
func (ms *MutableStateImpl) shouldVerifyChecksum() bool {
if ms.namespaceEntry == nil {
return false
}
return rand.Intn(100) < ms.config.MutableStateChecksumVerifyProbability(ms.namespaceEntry.Name().String())
}
func (ms *MutableStateImpl) shouldInvalidateCheckum() bool {
invalidateBeforeEpochSecs := int64(ms.config.MutableStateChecksumInvalidateBefore())
if invalidateBeforeEpochSecs > 0 {
invalidateBefore := time.Unix(invalidateBeforeEpochSecs, 0).UTC()
return ms.executionInfo.LastUpdateTime.AsTime().Before(invalidateBefore)
}
return false
}
func (ms *MutableStateImpl) createInternalServerError(
actionTag tag.ZapTag,
) error {
return serviceerror.NewInternal(actionTag.Field().String + " operation failed")
}
func (ms *MutableStateImpl) createCallerError(
actionTag tag.ZapTag,
details string,
) error {
msg := fmt.Sprintf(mutableStateInvalidHistoryActionMsgTemplate, actionTag.Field().String, details)
return serviceerror.NewInvalidArgument(msg)
}
func (ms *MutableStateImpl) logInfo(msg string, tags ...tag.Tag) {
tags = append(tags, tag.WorkflowID(ms.executionInfo.WorkflowId))
tags = append(tags, tag.WorkflowRunID(ms.executionState.RunId))
tags = append(tags, tag.WorkflowNamespaceID(ms.executionInfo.NamespaceId))
ms.logger.Info(msg, tags...)
}
func (ms *MutableStateImpl) logWarn(msg string, tags ...tag.Tag) {
tags = append(tags, tag.WorkflowID(ms.executionInfo.WorkflowId))
tags = append(tags, tag.WorkflowRunID(ms.executionState.RunId))
tags = append(tags, tag.WorkflowNamespaceID(ms.executionInfo.NamespaceId))
ms.logger.Warn(msg, tags...)
}
func (ms *MutableStateImpl) logError(msg string, tags ...tag.Tag) {
tags = append(tags, tag.WorkflowID(ms.executionInfo.WorkflowId))
tags = append(tags, tag.WorkflowRunID(ms.executionState.RunId))
tags = append(tags, tag.WorkflowNamespaceID(ms.executionInfo.NamespaceId))
ms.logger.Error(msg, tags...)
}
func (ms *MutableStateImpl) logDataInconsistency() {
namespaceID := ms.executionInfo.NamespaceId
workflowID := ms.executionInfo.WorkflowId
runID := ms.executionState.RunId
ms.logger.Error("encounter cassandra data inconsistency",
tag.WorkflowNamespaceID(namespaceID),
tag.WorkflowID(workflowID),
tag.WorkflowRunID(runID),
)
}