mirror of
https://github.com/hanzoai/tasks.git
synced 2026-10-30 14:56:19 +00:00
552 lines
14 KiB
Go
552 lines
14 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 nDCTransactionMgrForExistingWorkflow_mock.go
|
|
|
|
package history
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"go.temporal.io/api/serviceerror"
|
|
|
|
"go.temporal.io/server/common/namespace"
|
|
"go.temporal.io/server/common/persistence"
|
|
"go.temporal.io/server/service/history/workflow"
|
|
)
|
|
|
|
type (
|
|
nDCTransactionMgrForExistingWorkflow interface {
|
|
dispatchForExistingWorkflow(
|
|
ctx context.Context,
|
|
now time.Time,
|
|
isWorkflowRebuilt bool,
|
|
targetWorkflow nDCWorkflow,
|
|
newWorkflow nDCWorkflow,
|
|
) error
|
|
}
|
|
|
|
nDCTransactionMgrForExistingWorkflowImpl struct {
|
|
transactionMgr nDCTransactionMgr
|
|
}
|
|
)
|
|
|
|
var _ nDCTransactionMgrForExistingWorkflow = (*nDCTransactionMgrForExistingWorkflowImpl)(nil)
|
|
|
|
func newNDCTransactionMgrForExistingWorkflow(
|
|
transactionMgr nDCTransactionMgr,
|
|
) *nDCTransactionMgrForExistingWorkflowImpl {
|
|
|
|
return &nDCTransactionMgrForExistingWorkflowImpl{
|
|
transactionMgr: transactionMgr,
|
|
}
|
|
}
|
|
|
|
func (r *nDCTransactionMgrForExistingWorkflowImpl) dispatchForExistingWorkflow(
|
|
ctx context.Context,
|
|
now time.Time,
|
|
isWorkflowRebuilt bool,
|
|
targetWorkflow nDCWorkflow,
|
|
newWorkflow nDCWorkflow,
|
|
) error {
|
|
|
|
// NOTE: this function does NOT mutate current workflow, target workflow or new workflow,
|
|
// workflow mutation is done in methods within executeTransaction function
|
|
|
|
// this is a performance optimization so most update does not need to
|
|
// check whether target workflow is current workflow by calling DB API
|
|
if !isWorkflowRebuilt && targetWorkflow.getMutableState().IsCurrentWorkflowGuaranteed() {
|
|
// NOTE: if target workflow is rebuilt, then IsCurrentWorkflowGuaranteed is not trustworthy
|
|
|
|
// update to current record, since target workflow is pointed by current record
|
|
return r.dispatchWorkflowUpdateAsCurrent(
|
|
ctx,
|
|
now,
|
|
isWorkflowRebuilt,
|
|
targetWorkflow,
|
|
newWorkflow,
|
|
)
|
|
}
|
|
|
|
targetExecutionInfo := targetWorkflow.getMutableState().GetExecutionInfo()
|
|
targetExecutionState := targetWorkflow.getMutableState().GetExecutionState()
|
|
namespaceID := namespace.ID(targetExecutionInfo.NamespaceId)
|
|
workflowID := targetExecutionInfo.WorkflowId
|
|
targetRunID := targetExecutionState.RunId
|
|
|
|
// the target workflow is rebuilt
|
|
// we need to check the current workflow execution
|
|
currentRunID, err := r.transactionMgr.getCurrentWorkflowRunID(
|
|
ctx,
|
|
namespaceID,
|
|
workflowID,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if currentRunID == "" {
|
|
// this means a bug in our code or DB is inconsistent...
|
|
return serviceerror.NewInternal("nDCTransactionMgr: unable to locate current workflow during update")
|
|
}
|
|
|
|
if currentRunID == targetRunID {
|
|
if !isWorkflowRebuilt {
|
|
return serviceerror.NewInternal("nDCTransactionMgr: encountered workflow not rebuilt & current workflow not guaranteed")
|
|
}
|
|
|
|
// update to current record, since target workflow is pointed by current record
|
|
return r.dispatchWorkflowUpdateAsCurrent(
|
|
ctx,
|
|
now,
|
|
isWorkflowRebuilt,
|
|
targetWorkflow,
|
|
newWorkflow,
|
|
)
|
|
}
|
|
|
|
// there exists a current workflow, need additional check
|
|
currentWorkflow, err := r.transactionMgr.loadNDCWorkflow(
|
|
ctx,
|
|
namespaceID,
|
|
workflowID,
|
|
currentRunID,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
targetWorkflowIsNewer, err := targetWorkflow.happensAfter(currentWorkflow)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !targetWorkflowIsNewer {
|
|
// target workflow is older than current workflow, need to suppress the target workflow
|
|
return r.dispatchWorkflowUpdateAsZombie(
|
|
ctx,
|
|
now,
|
|
isWorkflowRebuilt,
|
|
currentWorkflow,
|
|
targetWorkflow,
|
|
newWorkflow,
|
|
)
|
|
}
|
|
|
|
// isWorkflowRebuilt is irrelevant here, because the DB API to be used
|
|
// will set target workflow using snapshot
|
|
return r.executeTransaction(
|
|
ctx,
|
|
now,
|
|
nDCTransactionPolicySuppressCurrentAndUpdateAsCurrent,
|
|
currentWorkflow,
|
|
targetWorkflow,
|
|
newWorkflow,
|
|
)
|
|
}
|
|
|
|
func (r *nDCTransactionMgrForExistingWorkflowImpl) dispatchWorkflowUpdateAsCurrent(
|
|
ctx context.Context,
|
|
now time.Time,
|
|
isWorkflowRebuilt bool,
|
|
targetWorkflow nDCWorkflow,
|
|
newWorkflow nDCWorkflow,
|
|
) error {
|
|
|
|
if !isWorkflowRebuilt {
|
|
return r.executeTransaction(
|
|
ctx,
|
|
now,
|
|
nDCTransactionPolicyUpdateAsCurrent,
|
|
nil,
|
|
targetWorkflow,
|
|
newWorkflow,
|
|
)
|
|
}
|
|
|
|
return r.executeTransaction(
|
|
ctx,
|
|
now,
|
|
nDCTransactionPolicyConflictResolveAsCurrent,
|
|
nil,
|
|
targetWorkflow,
|
|
newWorkflow,
|
|
)
|
|
}
|
|
|
|
func (r *nDCTransactionMgrForExistingWorkflowImpl) dispatchWorkflowUpdateAsZombie(
|
|
ctx context.Context,
|
|
now time.Time,
|
|
isWorkflowRebuilt bool,
|
|
currentWorkflow nDCWorkflow,
|
|
targetWorkflow nDCWorkflow,
|
|
newWorkflow nDCWorkflow,
|
|
) error {
|
|
|
|
if !isWorkflowRebuilt {
|
|
return r.executeTransaction(
|
|
ctx,
|
|
now,
|
|
nDCTransactionPolicyUpdateAsZombie,
|
|
currentWorkflow,
|
|
targetWorkflow,
|
|
newWorkflow,
|
|
)
|
|
}
|
|
|
|
return r.executeTransaction(
|
|
ctx,
|
|
now,
|
|
nDCTransactionPolicyConflictResolveAsZombie,
|
|
currentWorkflow,
|
|
targetWorkflow,
|
|
newWorkflow,
|
|
)
|
|
}
|
|
|
|
func (r *nDCTransactionMgrForExistingWorkflowImpl) updateAsCurrent(
|
|
ctx context.Context,
|
|
now time.Time,
|
|
targetWorkflow nDCWorkflow,
|
|
newWorkflow nDCWorkflow,
|
|
) error {
|
|
|
|
if newWorkflow == nil {
|
|
return targetWorkflow.getContext().UpdateWorkflowExecutionAsPassive(ctx, now)
|
|
}
|
|
|
|
return targetWorkflow.getContext().UpdateWorkflowExecutionWithNewAsPassive(
|
|
ctx,
|
|
now,
|
|
newWorkflow.getContext(),
|
|
newWorkflow.getMutableState(),
|
|
)
|
|
}
|
|
|
|
func (r *nDCTransactionMgrForExistingWorkflowImpl) updateAsZombie(
|
|
ctx context.Context,
|
|
now time.Time,
|
|
currentWorkflow nDCWorkflow,
|
|
targetWorkflow nDCWorkflow,
|
|
newWorkflow nDCWorkflow,
|
|
) error {
|
|
|
|
targetPolicy, err := targetWorkflow.suppressBy(
|
|
currentWorkflow,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if targetPolicy != workflow.TransactionPolicyPassive {
|
|
return serviceerror.NewInternal("nDCTransactionMgrForExistingWorkflow updateAsZombie encountered target workflow policy not being passive")
|
|
}
|
|
|
|
var newContext workflow.Context
|
|
var newMutableState workflow.MutableState
|
|
var newTransactionPolicy *workflow.TransactionPolicy
|
|
if newWorkflow != nil {
|
|
newWorkflowPolicy, err := newWorkflow.suppressBy(
|
|
currentWorkflow,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if newWorkflowPolicy != workflow.TransactionPolicyPassive {
|
|
return serviceerror.NewInternal("nDCTransactionMgrForExistingWorkflow updateAsZombie encountered new workflow policy not being passive")
|
|
}
|
|
|
|
// sanity check if new workflow is already created
|
|
// since workflow resend can have already created the new workflow
|
|
newExecutionInfo := newWorkflow.getMutableState().GetExecutionInfo()
|
|
newExecutionState := newWorkflow.getMutableState().GetExecutionState()
|
|
newWorkflowExists, err := r.transactionMgr.checkWorkflowExists(
|
|
ctx,
|
|
namespace.ID(newExecutionInfo.NamespaceId),
|
|
newExecutionInfo.WorkflowId,
|
|
newExecutionState.RunId,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if newWorkflowExists {
|
|
// new workflow already exists, do not create again
|
|
newContext = nil
|
|
newMutableState = nil
|
|
newTransactionPolicy = nil
|
|
} else {
|
|
// new workflow does not exists, continue
|
|
newContext = newWorkflow.getContext()
|
|
newMutableState = newWorkflow.getMutableState()
|
|
newTransactionPolicy = workflow.TransactionPolicyPassive.Ptr()
|
|
}
|
|
}
|
|
|
|
// release lock on current workflow, since current cluster maybe the active cluster
|
|
// and events maybe reapplied to current workflow
|
|
currentWorkflow.getReleaseFn()(nil)
|
|
currentWorkflow = nil
|
|
|
|
return targetWorkflow.getContext().UpdateWorkflowExecutionWithNew(
|
|
ctx,
|
|
now,
|
|
persistence.UpdateWorkflowModeBypassCurrent,
|
|
newContext,
|
|
newMutableState,
|
|
workflow.TransactionPolicyPassive,
|
|
newTransactionPolicy,
|
|
)
|
|
}
|
|
|
|
func (r *nDCTransactionMgrForExistingWorkflowImpl) suppressCurrentAndUpdateAsCurrent(
|
|
ctx context.Context,
|
|
now time.Time,
|
|
currentWorkflow nDCWorkflow,
|
|
targetWorkflow nDCWorkflow,
|
|
newWorkflow nDCWorkflow,
|
|
) error {
|
|
|
|
var err error
|
|
|
|
currentWorkflowPolicy := workflow.TransactionPolicyPassive
|
|
if currentWorkflow.getMutableState().IsWorkflowExecutionRunning() {
|
|
currentWorkflowPolicy, err = currentWorkflow.suppressBy(
|
|
targetWorkflow,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if err := targetWorkflow.revive(); err != nil {
|
|
return err
|
|
}
|
|
|
|
var newContext workflow.Context
|
|
var newMutableState workflow.MutableState
|
|
if newWorkflow != nil {
|
|
newContext = newWorkflow.getContext()
|
|
newMutableState = newWorkflow.getMutableState()
|
|
if err := newWorkflow.revive(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return targetWorkflow.getContext().ConflictResolveWorkflowExecution(
|
|
ctx,
|
|
now,
|
|
persistence.ConflictResolveWorkflowModeUpdateCurrent,
|
|
targetWorkflow.getMutableState(),
|
|
newContext,
|
|
newMutableState,
|
|
currentWorkflow.getContext(),
|
|
currentWorkflow.getMutableState(),
|
|
currentWorkflowPolicy.Ptr(),
|
|
)
|
|
}
|
|
|
|
func (r *nDCTransactionMgrForExistingWorkflowImpl) conflictResolveAsCurrent(
|
|
ctx context.Context,
|
|
now time.Time,
|
|
targetWorkflow nDCWorkflow,
|
|
newWorkflow nDCWorkflow,
|
|
) error {
|
|
|
|
var newContext workflow.Context
|
|
var newMutableState workflow.MutableState
|
|
if newWorkflow != nil {
|
|
newContext = newWorkflow.getContext()
|
|
newMutableState = newWorkflow.getMutableState()
|
|
}
|
|
|
|
return targetWorkflow.getContext().ConflictResolveWorkflowExecution(
|
|
ctx,
|
|
now,
|
|
persistence.ConflictResolveWorkflowModeUpdateCurrent,
|
|
targetWorkflow.getMutableState(),
|
|
newContext,
|
|
newMutableState,
|
|
nil,
|
|
nil,
|
|
nil,
|
|
)
|
|
}
|
|
|
|
func (r *nDCTransactionMgrForExistingWorkflowImpl) conflictResolveAsZombie(
|
|
ctx context.Context,
|
|
now time.Time,
|
|
currentWorkflow nDCWorkflow,
|
|
targetWorkflow nDCWorkflow,
|
|
newWorkflow nDCWorkflow,
|
|
) error {
|
|
|
|
targetWorkflowPolicy, err := targetWorkflow.suppressBy(
|
|
currentWorkflow,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if targetWorkflowPolicy != workflow.TransactionPolicyPassive {
|
|
return serviceerror.NewInternal("nDCTransactionMgrForExistingWorkflow conflictResolveAsZombie encountered target workflow policy not being passive")
|
|
}
|
|
|
|
var newContext workflow.Context
|
|
var newMutableState workflow.MutableState
|
|
if newWorkflow != nil {
|
|
newWorkflowPolicy, err := newWorkflow.suppressBy(
|
|
currentWorkflow,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if newWorkflowPolicy != workflow.TransactionPolicyPassive {
|
|
return serviceerror.NewInternal("nDCTransactionMgrForExistingWorkflow conflictResolveAsZombie encountered new workflow policy not being passive")
|
|
}
|
|
|
|
// sanity check if new workflow is already created
|
|
// since workflow resend can have already created the new workflow
|
|
newExecutionInfo := newWorkflow.getMutableState().GetExecutionInfo()
|
|
newExecutionState := newWorkflow.getMutableState().GetExecutionState()
|
|
newWorkflowExists, err := r.transactionMgr.checkWorkflowExists(
|
|
ctx,
|
|
namespace.ID(newExecutionInfo.NamespaceId),
|
|
newExecutionInfo.WorkflowId,
|
|
newExecutionState.RunId,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if newWorkflowExists {
|
|
// new workflow already exists, do not create again
|
|
newContext = nil
|
|
newMutableState = nil
|
|
} else {
|
|
// new workflow does not exist, continue
|
|
newContext = newWorkflow.getContext()
|
|
newMutableState = newWorkflow.getMutableState()
|
|
}
|
|
}
|
|
|
|
// release lock on current workflow, since current cluster maybe the active cluster
|
|
// and events maybe reapplied to current workflow
|
|
currentWorkflow.getReleaseFn()(nil)
|
|
currentWorkflow = nil
|
|
|
|
return targetWorkflow.getContext().ConflictResolveWorkflowExecution(
|
|
ctx,
|
|
now,
|
|
persistence.ConflictResolveWorkflowModeBypassCurrent,
|
|
targetWorkflow.getMutableState(),
|
|
newContext,
|
|
newMutableState,
|
|
nil,
|
|
nil,
|
|
nil,
|
|
)
|
|
}
|
|
|
|
func (r *nDCTransactionMgrForExistingWorkflowImpl) executeTransaction(
|
|
ctx context.Context,
|
|
now time.Time,
|
|
transactionPolicy nDCTransactionPolicy,
|
|
currentWorkflow nDCWorkflow,
|
|
targetWorkflow nDCWorkflow,
|
|
newWorkflow nDCWorkflow,
|
|
) (retError error) {
|
|
|
|
defer func() {
|
|
if rec := recover(); rec != nil {
|
|
r.cleanupTransaction(currentWorkflow, targetWorkflow, newWorkflow, errPanic)
|
|
panic(rec)
|
|
} else {
|
|
r.cleanupTransaction(currentWorkflow, targetWorkflow, newWorkflow, retError)
|
|
}
|
|
}()
|
|
|
|
switch transactionPolicy {
|
|
case nDCTransactionPolicyUpdateAsCurrent:
|
|
return r.updateAsCurrent(
|
|
ctx,
|
|
now,
|
|
targetWorkflow,
|
|
newWorkflow,
|
|
)
|
|
|
|
case nDCTransactionPolicyUpdateAsZombie:
|
|
return r.updateAsZombie(
|
|
ctx,
|
|
now,
|
|
currentWorkflow,
|
|
targetWorkflow,
|
|
newWorkflow,
|
|
)
|
|
|
|
case nDCTransactionPolicySuppressCurrentAndUpdateAsCurrent:
|
|
return r.suppressCurrentAndUpdateAsCurrent(
|
|
ctx,
|
|
now,
|
|
currentWorkflow,
|
|
targetWorkflow,
|
|
newWorkflow,
|
|
)
|
|
|
|
case nDCTransactionPolicyConflictResolveAsCurrent:
|
|
return r.conflictResolveAsCurrent(
|
|
ctx,
|
|
now,
|
|
targetWorkflow,
|
|
newWorkflow,
|
|
)
|
|
|
|
case nDCTransactionPolicyConflictResolveAsZombie:
|
|
return r.conflictResolveAsZombie(
|
|
ctx,
|
|
now,
|
|
currentWorkflow,
|
|
targetWorkflow,
|
|
newWorkflow,
|
|
)
|
|
|
|
default:
|
|
return serviceerror.NewInternal(fmt.Sprintf("nDCTransactionMgr: encountered unknown transaction type: %v", transactionPolicy))
|
|
}
|
|
}
|
|
|
|
func (r *nDCTransactionMgrForExistingWorkflowImpl) cleanupTransaction(
|
|
currentWorkflow nDCWorkflow,
|
|
targetWorkflow nDCWorkflow,
|
|
newWorkflow nDCWorkflow,
|
|
err error,
|
|
) {
|
|
|
|
if currentWorkflow != nil {
|
|
currentWorkflow.getReleaseFn()(err)
|
|
}
|
|
if targetWorkflow != nil {
|
|
targetWorkflow.getReleaseFn()(err)
|
|
}
|
|
if newWorkflow != nil {
|
|
newWorkflow.getReleaseFn()(err)
|
|
}
|
|
}
|