mirror of
https://github.com/hanzoai/tasks.git
synced 2026-10-30 14:56:19 +00:00
1012 lines
28 KiB
Go
1012 lines
28 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.
|
|
|
|
//go:generate mockgen -copyright_file ../../../LICENSE -package $GOPACKAGE -source $GOFILE -destination context_mock.go
|
|
|
|
package workflow
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"go.opentelemetry.io/otel/trace"
|
|
commonpb "go.temporal.io/api/common/v1"
|
|
enumspb "go.temporal.io/api/enums/v1"
|
|
historypb "go.temporal.io/api/history/v1"
|
|
"go.temporal.io/api/serviceerror"
|
|
|
|
"go.temporal.io/server/api/adminservice/v1"
|
|
persistencespb "go.temporal.io/server/api/persistence/v1"
|
|
"go.temporal.io/server/common"
|
|
"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/locks"
|
|
"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"
|
|
"go.temporal.io/server/common/persistence"
|
|
"go.temporal.io/server/service/history/configs"
|
|
"go.temporal.io/server/service/history/consts"
|
|
"go.temporal.io/server/service/history/shard"
|
|
"go.temporal.io/server/service/history/tasks"
|
|
"go.temporal.io/server/service/history/workflow/update"
|
|
)
|
|
|
|
const (
|
|
defaultRemoteCallTimeout = 30 * time.Second
|
|
)
|
|
|
|
const (
|
|
LockPriorityHigh LockPriority = 0
|
|
LockPriorityLow LockPriority = 1
|
|
)
|
|
|
|
type (
|
|
LockPriority int
|
|
|
|
Context interface {
|
|
GetWorkflowKey() definition.WorkflowKey
|
|
|
|
LoadMutableState(ctx context.Context) (MutableState, error)
|
|
LoadExecutionStats(ctx context.Context) (*persistencespb.ExecutionStats, error)
|
|
Clear()
|
|
|
|
Lock(ctx context.Context, lockPriority LockPriority) error
|
|
Unlock(lockPriority LockPriority)
|
|
|
|
ReapplyEvents(
|
|
ctx context.Context,
|
|
eventBatches []*persistence.WorkflowEvents,
|
|
) error
|
|
|
|
PersistWorkflowEvents(
|
|
ctx context.Context,
|
|
workflowEventsSlice ...*persistence.WorkflowEvents,
|
|
) (int64, error)
|
|
|
|
CreateWorkflowExecution(
|
|
ctx context.Context,
|
|
createMode persistence.CreateWorkflowMode,
|
|
prevRunID string,
|
|
prevLastWriteVersion int64,
|
|
newMutableState MutableState,
|
|
newWorkflow *persistence.WorkflowSnapshot,
|
|
newWorkflowEvents []*persistence.WorkflowEvents,
|
|
) error
|
|
ConflictResolveWorkflowExecution(
|
|
ctx context.Context,
|
|
conflictResolveMode persistence.ConflictResolveWorkflowMode,
|
|
resetMutableState MutableState,
|
|
newContext Context,
|
|
newMutableState MutableState,
|
|
currentContext Context,
|
|
currentMutableState MutableState,
|
|
currentTransactionPolicy *TransactionPolicy,
|
|
) error
|
|
UpdateWorkflowExecutionAsActive(
|
|
ctx context.Context,
|
|
) error
|
|
UpdateWorkflowExecutionWithNewAsActive(
|
|
ctx context.Context,
|
|
newContext Context,
|
|
newMutableState MutableState,
|
|
) error
|
|
UpdateWorkflowExecutionAsPassive(
|
|
ctx context.Context,
|
|
) error
|
|
UpdateWorkflowExecutionWithNewAsPassive(
|
|
ctx context.Context,
|
|
newContext Context,
|
|
newMutableState MutableState,
|
|
) error
|
|
UpdateWorkflowExecutionWithNew(
|
|
ctx context.Context,
|
|
updateMode persistence.UpdateWorkflowMode,
|
|
newContext Context,
|
|
newMutableState MutableState,
|
|
currentWorkflowTransactionPolicy TransactionPolicy,
|
|
newWorkflowTransactionPolicy *TransactionPolicy,
|
|
) error
|
|
SetWorkflowExecution(
|
|
ctx context.Context,
|
|
) error
|
|
// TODO (alex-update): move this from workflow context.
|
|
UpdateRegistry(ctx context.Context) update.Registry
|
|
}
|
|
)
|
|
|
|
type (
|
|
ContextImpl struct {
|
|
shard shard.Context
|
|
workflowKey definition.WorkflowKey
|
|
logger log.Logger
|
|
throttledLogger log.ThrottledLogger
|
|
metricsHandler metrics.Handler
|
|
clusterMetadata cluster.Metadata
|
|
timeSource clock.TimeSource
|
|
config *configs.Config
|
|
transaction Transaction
|
|
|
|
mutex locks.PriorityMutex
|
|
MutableState MutableState
|
|
updateRegistry update.Registry
|
|
}
|
|
)
|
|
|
|
var _ Context = (*ContextImpl)(nil)
|
|
|
|
func NewContext(
|
|
shard shard.Context,
|
|
workflowKey definition.WorkflowKey,
|
|
logger log.Logger,
|
|
) *ContextImpl {
|
|
return &ContextImpl{
|
|
shard: shard,
|
|
workflowKey: workflowKey,
|
|
logger: logger,
|
|
throttledLogger: shard.GetThrottledLogger(),
|
|
metricsHandler: shard.GetMetricsHandler().WithTags(metrics.OperationTag(metrics.WorkflowContextScope)),
|
|
clusterMetadata: shard.GetClusterMetadata(),
|
|
timeSource: shard.GetTimeSource(),
|
|
config: shard.GetConfig(),
|
|
mutex: locks.NewPriorityMutex(),
|
|
transaction: NewTransaction(shard),
|
|
}
|
|
}
|
|
|
|
func (c *ContextImpl) Lock(
|
|
ctx context.Context,
|
|
lockPriority LockPriority,
|
|
) error {
|
|
switch lockPriority {
|
|
case LockPriorityHigh:
|
|
return c.mutex.LockHigh(ctx)
|
|
case LockPriorityLow:
|
|
return c.mutex.LockLow(ctx)
|
|
default:
|
|
panic(fmt.Sprintf("unknown lock priority: %v", lockPriority))
|
|
}
|
|
}
|
|
|
|
func (c *ContextImpl) Unlock(
|
|
lockPriority LockPriority,
|
|
) {
|
|
switch lockPriority {
|
|
case LockPriorityHigh:
|
|
c.mutex.UnlockHigh()
|
|
case LockPriorityLow:
|
|
c.mutex.UnlockLow()
|
|
default:
|
|
panic(fmt.Sprintf("unknown lock priority: %v", lockPriority))
|
|
}
|
|
}
|
|
|
|
func (c *ContextImpl) Clear() {
|
|
c.metricsHandler.Counter(metrics.WorkflowContextCleared.GetMetricName()).Record(1)
|
|
if c.MutableState != nil {
|
|
c.MutableState.GetQueryRegistry().Clear()
|
|
}
|
|
c.MutableState = nil
|
|
}
|
|
|
|
func (c *ContextImpl) GetWorkflowKey() definition.WorkflowKey {
|
|
return c.workflowKey
|
|
}
|
|
|
|
func (c *ContextImpl) GetNamespace() namespace.Name {
|
|
namespaceEntry, err := c.shard.GetNamespaceRegistry().GetNamespaceByID(
|
|
namespace.ID(c.workflowKey.NamespaceID),
|
|
)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return namespaceEntry.Name()
|
|
}
|
|
|
|
func (c *ContextImpl) LoadExecutionStats(ctx context.Context) (*persistencespb.ExecutionStats, error) {
|
|
_, err := c.LoadMutableState(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return c.MutableState.GetExecutionInfo().ExecutionStats, nil
|
|
}
|
|
|
|
func (c *ContextImpl) LoadMutableState(ctx context.Context) (MutableState, error) {
|
|
namespaceEntry, err := c.shard.GetNamespaceRegistry().GetNamespaceByID(
|
|
namespace.ID(c.workflowKey.NamespaceID),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if c.MutableState == nil {
|
|
response, err := getWorkflowExecution(ctx, c.shard, &persistence.GetWorkflowExecutionRequest{
|
|
ShardID: c.shard.GetShardID(),
|
|
NamespaceID: c.workflowKey.NamespaceID,
|
|
WorkflowID: c.workflowKey.WorkflowID,
|
|
RunID: c.workflowKey.RunID,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
c.MutableState, err = newMutableStateFromDB(
|
|
c.shard,
|
|
c.shard.GetEventsCache(),
|
|
c.logger,
|
|
namespaceEntry,
|
|
response.State,
|
|
response.DBRecordVersion,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
flushBeforeReady, err := c.MutableState.StartTransaction(namespaceEntry)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !flushBeforeReady {
|
|
return c.MutableState, nil
|
|
}
|
|
|
|
if err = c.UpdateWorkflowExecutionAsActive(
|
|
ctx,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
flushBeforeReady, err = c.MutableState.StartTransaction(namespaceEntry)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if flushBeforeReady {
|
|
return nil, serviceerror.NewInternal("Context counter flushBeforeReady status after loading mutable state from DB")
|
|
}
|
|
|
|
return c.MutableState, nil
|
|
}
|
|
|
|
func (c *ContextImpl) PersistWorkflowEvents(
|
|
ctx context.Context,
|
|
workflowEventsSlice ...*persistence.WorkflowEvents,
|
|
) (int64, error) {
|
|
return PersistWorkflowEvents(ctx, c.shard, workflowEventsSlice...)
|
|
}
|
|
|
|
func (c *ContextImpl) CreateWorkflowExecution(
|
|
ctx context.Context,
|
|
createMode persistence.CreateWorkflowMode,
|
|
prevRunID string,
|
|
prevLastWriteVersion int64,
|
|
newMutableState MutableState,
|
|
newWorkflow *persistence.WorkflowSnapshot,
|
|
newWorkflowEvents []*persistence.WorkflowEvents,
|
|
) (retError error) {
|
|
|
|
defer func() {
|
|
if retError != nil {
|
|
c.Clear()
|
|
}
|
|
}()
|
|
|
|
createRequest := &persistence.CreateWorkflowExecutionRequest{
|
|
ShardID: c.shard.GetShardID(),
|
|
// workflow create mode & prev run ID & version
|
|
Mode: createMode,
|
|
PreviousRunID: prevRunID,
|
|
PreviousLastWriteVersion: prevLastWriteVersion,
|
|
|
|
NewWorkflowSnapshot: *newWorkflow,
|
|
NewWorkflowEvents: newWorkflowEvents,
|
|
}
|
|
|
|
_, err := createWorkflowExecution(
|
|
ctx,
|
|
c.shard,
|
|
newMutableState.GetCurrentVersion(),
|
|
createRequest,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
engine, err := c.shard.GetEngine(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
NotifyWorkflowSnapshotTasks(engine, newWorkflow)
|
|
emitStateTransitionCount(c.metricsHandler, c.clusterMetadata, newMutableState)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *ContextImpl) ConflictResolveWorkflowExecution(
|
|
ctx context.Context,
|
|
conflictResolveMode persistence.ConflictResolveWorkflowMode,
|
|
resetMutableState MutableState,
|
|
newContext Context,
|
|
newMutableState MutableState,
|
|
currentContext Context,
|
|
currentMutableState MutableState,
|
|
currentTransactionPolicy *TransactionPolicy,
|
|
) (retError error) {
|
|
|
|
defer func() {
|
|
if retError != nil {
|
|
c.Clear()
|
|
}
|
|
}()
|
|
|
|
resetWorkflow, resetWorkflowEventsSeq, err := resetMutableState.CloseTransactionAsSnapshot(
|
|
TransactionPolicyPassive,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var newWorkflow *persistence.WorkflowSnapshot
|
|
var newWorkflowEventsSeq []*persistence.WorkflowEvents
|
|
if newContext != nil && newMutableState != nil {
|
|
|
|
defer func() {
|
|
if retError != nil {
|
|
newContext.Clear()
|
|
}
|
|
}()
|
|
|
|
newWorkflow, newWorkflowEventsSeq, err = newMutableState.CloseTransactionAsSnapshot(
|
|
TransactionPolicyPassive,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
var currentWorkflow *persistence.WorkflowMutation
|
|
var currentWorkflowEventsSeq []*persistence.WorkflowEvents
|
|
if currentContext != nil && currentMutableState != nil && currentTransactionPolicy != nil {
|
|
|
|
defer func() {
|
|
if retError != nil {
|
|
currentContext.Clear()
|
|
}
|
|
}()
|
|
|
|
currentWorkflow, currentWorkflowEventsSeq, err = currentMutableState.CloseTransactionAsMutation(
|
|
*currentTransactionPolicy,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err := c.conflictResolveEventReapply(
|
|
ctx,
|
|
conflictResolveMode,
|
|
resetWorkflowEventsSeq,
|
|
newWorkflowEventsSeq,
|
|
// current workflow events will not participate in the events reapplication
|
|
); err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, _, _, err := c.transaction.ConflictResolveWorkflowExecution(
|
|
ctx,
|
|
conflictResolveMode,
|
|
resetMutableState.GetCurrentVersion(),
|
|
resetWorkflow,
|
|
resetWorkflowEventsSeq,
|
|
MutableStateFailoverVersion(newMutableState),
|
|
newWorkflow,
|
|
newWorkflowEventsSeq,
|
|
MutableStateFailoverVersion(currentMutableState),
|
|
currentWorkflow,
|
|
currentWorkflowEventsSeq,
|
|
); err != nil {
|
|
return err
|
|
}
|
|
|
|
emitStateTransitionCount(c.metricsHandler, c.clusterMetadata, resetMutableState)
|
|
emitStateTransitionCount(c.metricsHandler, c.clusterMetadata, newMutableState)
|
|
emitStateTransitionCount(c.metricsHandler, c.clusterMetadata, currentMutableState)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *ContextImpl) UpdateWorkflowExecutionAsActive(
|
|
ctx context.Context,
|
|
) error {
|
|
|
|
// We only perform this check on active cluster for the namespace
|
|
historyForceTerminate, err := c.enforceHistorySizeCheck(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
msForceTerminate := false
|
|
if !historyForceTerminate {
|
|
msForceTerminate, err = c.enforceMutableStateSizeCheck(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
err = c.UpdateWorkflowExecutionWithNew(
|
|
ctx,
|
|
persistence.UpdateWorkflowModeUpdateCurrent,
|
|
nil,
|
|
nil,
|
|
TransactionPolicyActive,
|
|
nil,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Returns ResourceExhausted error back to caller after workflow execution is forced terminated
|
|
// Retrying the operation will give appropriate semantics operation should expect in the case of workflow
|
|
// execution being closed.
|
|
if historyForceTerminate {
|
|
return consts.ErrHistorySizeExceedsLimit
|
|
}
|
|
if msForceTerminate {
|
|
return consts.ErrMutableStateSizeExceedsLimit
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *ContextImpl) UpdateWorkflowExecutionWithNewAsActive(
|
|
ctx context.Context,
|
|
newContext Context,
|
|
newMutableState MutableState,
|
|
) error {
|
|
|
|
return c.UpdateWorkflowExecutionWithNew(
|
|
ctx,
|
|
persistence.UpdateWorkflowModeUpdateCurrent,
|
|
newContext,
|
|
newMutableState,
|
|
TransactionPolicyActive,
|
|
TransactionPolicyActive.Ptr(),
|
|
)
|
|
}
|
|
|
|
func (c *ContextImpl) UpdateWorkflowExecutionAsPassive(
|
|
ctx context.Context,
|
|
) error {
|
|
|
|
return c.UpdateWorkflowExecutionWithNew(
|
|
ctx,
|
|
persistence.UpdateWorkflowModeUpdateCurrent,
|
|
nil,
|
|
nil,
|
|
TransactionPolicyPassive,
|
|
nil,
|
|
)
|
|
}
|
|
|
|
func (c *ContextImpl) UpdateWorkflowExecutionWithNewAsPassive(
|
|
ctx context.Context,
|
|
newContext Context,
|
|
newMutableState MutableState,
|
|
) error {
|
|
|
|
return c.UpdateWorkflowExecutionWithNew(
|
|
ctx,
|
|
persistence.UpdateWorkflowModeUpdateCurrent,
|
|
newContext,
|
|
newMutableState,
|
|
TransactionPolicyPassive,
|
|
TransactionPolicyPassive.Ptr(),
|
|
)
|
|
}
|
|
|
|
func (c *ContextImpl) UpdateWorkflowExecutionWithNew(
|
|
ctx context.Context,
|
|
updateMode persistence.UpdateWorkflowMode,
|
|
newContext Context,
|
|
newMutableState MutableState,
|
|
currentWorkflowTransactionPolicy TransactionPolicy,
|
|
newWorkflowTransactionPolicy *TransactionPolicy,
|
|
) (retError error) {
|
|
|
|
defer func() {
|
|
if retError != nil {
|
|
c.Clear()
|
|
}
|
|
}()
|
|
|
|
currentWorkflow, currentWorkflowEventsSeq, err := c.MutableState.CloseTransactionAsMutation(
|
|
currentWorkflowTransactionPolicy,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var newWorkflow *persistence.WorkflowSnapshot
|
|
var newWorkflowEventsSeq []*persistence.WorkflowEvents
|
|
if newContext != nil && newMutableState != nil && newWorkflowTransactionPolicy != nil {
|
|
defer func() {
|
|
if retError != nil {
|
|
newContext.Clear()
|
|
}
|
|
}()
|
|
|
|
newWorkflow, newWorkflowEventsSeq, err = newMutableState.CloseTransactionAsSnapshot(
|
|
*newWorkflowTransactionPolicy,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err := c.mergeContinueAsNewReplicationTasks(
|
|
updateMode,
|
|
currentWorkflow,
|
|
newWorkflow,
|
|
); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := c.updateWorkflowExecutionEventReapply(
|
|
ctx,
|
|
updateMode,
|
|
currentWorkflowEventsSeq,
|
|
newWorkflowEventsSeq,
|
|
); err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, _, err := c.transaction.UpdateWorkflowExecution(
|
|
ctx,
|
|
updateMode,
|
|
c.MutableState.GetCurrentVersion(),
|
|
currentWorkflow,
|
|
currentWorkflowEventsSeq,
|
|
MutableStateFailoverVersion(newMutableState),
|
|
newWorkflow,
|
|
newWorkflowEventsSeq,
|
|
); err != nil {
|
|
return err
|
|
}
|
|
|
|
emitStateTransitionCount(c.metricsHandler, c.clusterMetadata, c.MutableState)
|
|
emitStateTransitionCount(c.metricsHandler, c.clusterMetadata, newMutableState)
|
|
|
|
// finally emit session stats
|
|
namespace := c.GetNamespace()
|
|
emitWorkflowHistoryStats(
|
|
c.metricsHandler,
|
|
namespace,
|
|
int(c.MutableState.GetExecutionInfo().ExecutionStats.HistorySize),
|
|
int(c.MutableState.GetNextEventID()-1),
|
|
)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *ContextImpl) SetWorkflowExecution(
|
|
ctx context.Context,
|
|
) (retError error) {
|
|
defer func() {
|
|
if retError != nil {
|
|
c.Clear()
|
|
}
|
|
}()
|
|
|
|
resetWorkflowSnapshot, resetWorkflowEventsSeq, err := c.MutableState.CloseTransactionAsSnapshot(
|
|
TransactionPolicyPassive,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(resetWorkflowEventsSeq) != 0 {
|
|
c.metricsHandler.Counter(metrics.ClosedWorkflowBufferEventCount.GetMetricName()).Record(1)
|
|
c.logger.Warn("SetWorkflowExecution encountered new events")
|
|
}
|
|
|
|
return c.transaction.SetWorkflowExecution(
|
|
ctx,
|
|
resetWorkflowSnapshot,
|
|
)
|
|
}
|
|
|
|
func (c *ContextImpl) mergeContinueAsNewReplicationTasks(
|
|
updateMode persistence.UpdateWorkflowMode,
|
|
currentWorkflowMutation *persistence.WorkflowMutation,
|
|
newWorkflowSnapshot *persistence.WorkflowSnapshot,
|
|
) error {
|
|
|
|
if currentWorkflowMutation.ExecutionState.Status != enumspb.WORKFLOW_EXECUTION_STATUS_CONTINUED_AS_NEW {
|
|
return nil
|
|
} else if updateMode == persistence.UpdateWorkflowModeBypassCurrent && newWorkflowSnapshot == nil {
|
|
// update current workflow as zombie & continue as new without new zombie workflow
|
|
// this case can be valid if new workflow is already created by resend
|
|
return nil
|
|
}
|
|
|
|
// current workflow is doing continue as new
|
|
|
|
// it is possible that continue as new is done as part of passive logic
|
|
if len(currentWorkflowMutation.Tasks[tasks.CategoryReplication]) == 0 {
|
|
return nil
|
|
}
|
|
|
|
if newWorkflowSnapshot == nil || len(newWorkflowSnapshot.Tasks[tasks.CategoryReplication]) != 1 {
|
|
return serviceerror.NewInternal("unable to find replication task from new workflow for continue as new replication")
|
|
}
|
|
|
|
// merge the new run first event batch replication task
|
|
// to current event batch replication task
|
|
newRunTask := newWorkflowSnapshot.Tasks[tasks.CategoryReplication][0].(*tasks.HistoryReplicationTask)
|
|
delete(newWorkflowSnapshot.Tasks, tasks.CategoryReplication)
|
|
|
|
newRunBranchToken := newRunTask.BranchToken
|
|
newRunID := newRunTask.RunID
|
|
taskUpdated := false
|
|
for _, replicationTask := range currentWorkflowMutation.Tasks[tasks.CategoryReplication] {
|
|
if task, ok := replicationTask.(*tasks.HistoryReplicationTask); ok {
|
|
taskUpdated = true
|
|
task.NewRunBranchToken = newRunBranchToken
|
|
task.NewRunID = newRunID
|
|
}
|
|
}
|
|
if !taskUpdated {
|
|
return serviceerror.NewInternal("unable to find replication task from current workflow for continue as new replication")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *ContextImpl) updateWorkflowExecutionEventReapply(
|
|
ctx context.Context,
|
|
updateMode persistence.UpdateWorkflowMode,
|
|
eventBatch1 []*persistence.WorkflowEvents,
|
|
eventBatch2 []*persistence.WorkflowEvents,
|
|
) error {
|
|
|
|
if updateMode != persistence.UpdateWorkflowModeBypassCurrent {
|
|
return nil
|
|
}
|
|
|
|
var eventBatches []*persistence.WorkflowEvents
|
|
eventBatches = append(eventBatches, eventBatch1...)
|
|
eventBatches = append(eventBatches, eventBatch2...)
|
|
return c.ReapplyEvents(ctx, eventBatches)
|
|
}
|
|
|
|
func (c *ContextImpl) conflictResolveEventReapply(
|
|
ctx context.Context,
|
|
conflictResolveMode persistence.ConflictResolveWorkflowMode,
|
|
eventBatch1 []*persistence.WorkflowEvents,
|
|
eventBatch2 []*persistence.WorkflowEvents,
|
|
) error {
|
|
|
|
if conflictResolveMode != persistence.ConflictResolveWorkflowModeBypassCurrent {
|
|
return nil
|
|
}
|
|
|
|
var eventBatches []*persistence.WorkflowEvents
|
|
eventBatches = append(eventBatches, eventBatch1...)
|
|
eventBatches = append(eventBatches, eventBatch2...)
|
|
return c.ReapplyEvents(ctx, eventBatches)
|
|
}
|
|
|
|
func (c *ContextImpl) ReapplyEvents(
|
|
ctx context.Context,
|
|
eventBatches []*persistence.WorkflowEvents,
|
|
) error {
|
|
|
|
// NOTE: this function should only be used to workflow which is
|
|
// not the caller, or otherwise deadlock will appear
|
|
|
|
if len(eventBatches) == 0 {
|
|
return nil
|
|
}
|
|
|
|
namespaceID := namespace.ID(eventBatches[0].NamespaceID)
|
|
workflowID := eventBatches[0].WorkflowID
|
|
runID := eventBatches[0].RunID
|
|
var reapplyEvents []*historypb.HistoryEvent
|
|
for _, events := range eventBatches {
|
|
if namespace.ID(events.NamespaceID) != namespaceID ||
|
|
events.WorkflowID != workflowID {
|
|
return serviceerror.NewInternal("Context encountered mismatch namespaceID / workflowID in events reapplication.")
|
|
}
|
|
|
|
for _, e := range events.Events {
|
|
event := e
|
|
switch event.GetEventType() {
|
|
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_SIGNALED:
|
|
reapplyEvents = append(reapplyEvents, event)
|
|
}
|
|
}
|
|
}
|
|
if len(reapplyEvents) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// Reapply events only reapply to the current run.
|
|
// The run id is only used for reapply event de-duplication
|
|
execution := &commonpb.WorkflowExecution{
|
|
WorkflowId: workflowID,
|
|
RunId: runID,
|
|
}
|
|
namespaceRegistry := c.shard.GetNamespaceRegistry()
|
|
serializer := c.shard.GetPayloadSerializer()
|
|
namespaceEntry, err := namespaceRegistry.GetNamespaceByID(namespaceID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
activeCluster := namespaceEntry.ActiveClusterName()
|
|
if activeCluster == c.shard.GetClusterMetadata().GetCurrentClusterName() {
|
|
engine, err := c.shard.GetEngine(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return engine.ReapplyEvents(
|
|
ctx,
|
|
namespaceID,
|
|
workflowID,
|
|
runID,
|
|
reapplyEvents,
|
|
)
|
|
}
|
|
|
|
// The active cluster of the namespace is the same as current cluster.
|
|
// Use the history from the same cluster to reapply events
|
|
reapplyEventsDataBlob, err := serializer.SerializeEvents(reapplyEvents, enumspb.ENCODING_TYPE_PROTO3)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// The active cluster of the namespace is differ from the current cluster
|
|
// Use frontend client to route this request to the active cluster
|
|
// Reapplication only happens in active cluster
|
|
sourceAdminClient, err := c.shard.GetRemoteAdminClient(activeCluster)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if sourceAdminClient == nil {
|
|
// TODO: will this ever happen?
|
|
return serviceerror.NewInternal(fmt.Sprintf("cannot find cluster config %v to do reapply", activeCluster))
|
|
}
|
|
|
|
_, err = sourceAdminClient.ReapplyEvents(
|
|
ctx,
|
|
&adminservice.ReapplyEventsRequest{
|
|
NamespaceId: namespaceEntry.ID().String(),
|
|
WorkflowExecution: execution,
|
|
Events: reapplyEventsDataBlob,
|
|
},
|
|
)
|
|
|
|
return err
|
|
}
|
|
|
|
func (c *ContextImpl) UpdateRegistry(ctx context.Context) update.Registry {
|
|
if c.updateRegistry == nil {
|
|
nsIDStr := c.MutableState.GetNamespaceEntry().ID().String()
|
|
c.updateRegistry = update.NewRegistry(
|
|
func() update.UpdateStore { return c.MutableState },
|
|
update.WithLogger(c.logger),
|
|
update.WithMetrics(c.metricsHandler),
|
|
update.WithTracerProvider(trace.SpanFromContext(ctx).TracerProvider()),
|
|
update.WithInFlightLimit(
|
|
func() int {
|
|
return c.config.WorkflowExecutionMaxInFlightUpdates(nsIDStr)
|
|
},
|
|
),
|
|
update.WithTotalLimit(
|
|
func() int {
|
|
return c.config.WorkflowExecutionMaxTotalUpdates(nsIDStr)
|
|
},
|
|
),
|
|
)
|
|
}
|
|
return c.updateRegistry
|
|
}
|
|
|
|
// Returns true if execution is forced terminated
|
|
func (c *ContextImpl) enforceHistorySizeCheck(
|
|
ctx context.Context,
|
|
) (bool, error) {
|
|
// Hard terminate workflow if still running and breached history size or history count limits
|
|
if c.maxHistorySizeExceeded() {
|
|
if err := c.forceTerminateWorkflow(ctx, common.FailureReasonHistorySizeExceedsLimit); err != nil {
|
|
return false, err
|
|
}
|
|
// Return true to caller to indicate workflow state is overwritten to force terminate execution on update
|
|
return true, nil
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
// Returns true if the workflow is running and history size or event count should trigger a forced termination
|
|
// Prints a log message if history size or history event count are over the error or warn limits
|
|
func (c *ContextImpl) maxHistorySizeExceeded() bool {
|
|
namespaceName := c.GetNamespace().String()
|
|
historySizeLimitWarn := c.config.HistorySizeLimitWarn(namespaceName)
|
|
historySizeLimitError := c.config.HistorySizeLimitError(namespaceName)
|
|
historyCountLimitWarn := c.config.HistoryCountLimitWarn(namespaceName)
|
|
historyCountLimitError := c.config.HistoryCountLimitError(namespaceName)
|
|
|
|
historySize := int(c.MutableState.GetExecutionInfo().ExecutionStats.HistorySize)
|
|
historyCount := int(c.MutableState.GetNextEventID() - 1)
|
|
|
|
if (historySize > historySizeLimitError || historyCount > historyCountLimitError) &&
|
|
c.MutableState.IsWorkflowExecutionRunning() {
|
|
c.logger.Warn("history size exceeds error limit.",
|
|
tag.WorkflowNamespaceID(c.workflowKey.NamespaceID),
|
|
tag.WorkflowID(c.workflowKey.WorkflowID),
|
|
tag.WorkflowRunID(c.workflowKey.RunID),
|
|
tag.WorkflowHistorySize(historySize),
|
|
tag.WorkflowEventCount(historyCount))
|
|
|
|
return true
|
|
}
|
|
|
|
if historySize > historySizeLimitWarn || historyCount > historyCountLimitWarn {
|
|
c.throttledLogger.Warn("history size exceeds warn limit.",
|
|
tag.WorkflowNamespaceID(c.MutableState.GetExecutionInfo().NamespaceId),
|
|
tag.WorkflowID(c.MutableState.GetExecutionInfo().WorkflowId),
|
|
tag.WorkflowRunID(c.MutableState.GetExecutionState().RunId),
|
|
tag.WorkflowHistorySize(historySize),
|
|
tag.WorkflowEventCount(historyCount))
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// Returns true if execution is forced terminated
|
|
// TODO: ideally this check should be after closing mutable state tx, but that would require a large refactor
|
|
func (c *ContextImpl) enforceMutableStateSizeCheck(ctx context.Context) (bool, error) {
|
|
if c.maxMutableStateSizeExceeded() {
|
|
if err := c.forceTerminateWorkflow(ctx, common.FailureReasonMutableStateSizeExceedsLimit); err != nil {
|
|
return false, err
|
|
}
|
|
// Return true to caller to indicate workflow state is overwritten to force terminate execution on update
|
|
return true, nil
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
// Returns true if the workflow is running and mutable state size should trigger a forced termination
|
|
// Prints a log message if mutable state size is over the error or warn limits
|
|
func (c *ContextImpl) maxMutableStateSizeExceeded() bool {
|
|
mutableStateSizeLimitError := c.config.MutableStateSizeLimitError()
|
|
mutableStateSizeLimitWarn := c.config.MutableStateSizeLimitWarn()
|
|
|
|
mutableStateSize := c.MutableState.GetApproximatePersistedSize()
|
|
|
|
if mutableStateSize > mutableStateSizeLimitError {
|
|
c.logger.Warn("mutable state size exceeds error limit.",
|
|
tag.WorkflowNamespaceID(c.workflowKey.NamespaceID),
|
|
tag.WorkflowID(c.workflowKey.WorkflowID),
|
|
tag.WorkflowRunID(c.workflowKey.RunID),
|
|
tag.WorkflowMutableStateSize(mutableStateSize))
|
|
|
|
return true
|
|
}
|
|
|
|
if mutableStateSize > mutableStateSizeLimitWarn {
|
|
c.throttledLogger.Warn("mutable state size exceeds warn limit.",
|
|
tag.WorkflowNamespaceID(c.MutableState.GetExecutionInfo().NamespaceId),
|
|
tag.WorkflowID(c.MutableState.GetExecutionInfo().WorkflowId),
|
|
tag.WorkflowRunID(c.MutableState.GetExecutionState().RunId),
|
|
tag.WorkflowMutableStateSize(mutableStateSize))
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (c *ContextImpl) forceTerminateWorkflow(
|
|
ctx context.Context,
|
|
failureReason string,
|
|
) error {
|
|
if !c.MutableState.IsWorkflowExecutionRunning() {
|
|
return nil
|
|
}
|
|
|
|
// Discard pending changes in MutableState so we can apply terminate state transition
|
|
c.Clear()
|
|
|
|
// Reload mutable state
|
|
mutableState, err := c.LoadMutableState(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return TerminateWorkflow(
|
|
mutableState,
|
|
failureReason,
|
|
nil,
|
|
consts.IdentityHistoryService,
|
|
false,
|
|
)
|
|
}
|
|
|
|
func emitStateTransitionCount(
|
|
metricsHandler metrics.Handler,
|
|
clusterMetadata cluster.Metadata,
|
|
mutableState MutableState,
|
|
) {
|
|
if mutableState == nil {
|
|
return
|
|
}
|
|
|
|
namespaceEntry := mutableState.GetNamespaceEntry()
|
|
metricsHandler.Histogram(
|
|
metrics.StateTransitionCount.GetMetricName(),
|
|
metrics.StateTransitionCount.GetMetricUnit(),
|
|
).Record(
|
|
mutableState.GetExecutionInfo().StateTransitionCount,
|
|
metrics.NamespaceTag(namespaceEntry.Name().String()),
|
|
metrics.NamespaceStateTag(namespaceState(clusterMetadata, convert.Int64Ptr(mutableState.GetCurrentVersion()))),
|
|
)
|
|
}
|
|
|
|
const (
|
|
namespaceStateActive = "active"
|
|
namespaceStatePassive = "passive"
|
|
namespaceStateUnknown = "_unknown_"
|
|
)
|
|
|
|
func namespaceState(
|
|
clusterMetadata cluster.Metadata,
|
|
mutableStateCurrentVersion *int64,
|
|
) string {
|
|
|
|
if mutableStateCurrentVersion == nil {
|
|
return namespaceStateUnknown
|
|
}
|
|
|
|
// default value, need to special handle
|
|
if *mutableStateCurrentVersion == 0 {
|
|
return namespaceStateActive
|
|
}
|
|
|
|
if clusterMetadata.IsVersionFromSameCluster(
|
|
clusterMetadata.GetClusterID(),
|
|
*mutableStateCurrentVersion,
|
|
) {
|
|
return namespaceStateActive
|
|
}
|
|
return namespaceStatePassive
|
|
}
|
|
|
|
func MutableStateFailoverVersion(
|
|
mutableState MutableState,
|
|
) *int64 {
|
|
if mutableState == nil {
|
|
return nil
|
|
}
|
|
return convert.Int64Ptr(mutableState.GetCurrentVersion())
|
|
}
|