mirror of
https://github.com/temporalio/temporal.git
synced 2026-08-30 18:41:49 -07:00
## What changed?
On the passive replication-apply path, when `dispatchForNewWorkflow`
finds no current execution record (`currentRunID == ""`), it now
preserves the incoming execution state and persists the run via
`CreateWorkflowModeBypassCurrent` in either of these cases:
- the incoming execution is already `ZOMBIE`; or
- the incoming execution is `COMPLETED` or `CORRUPTED` and already
records a successor through `NewExecutionRunId` or `SuccessorRunId`.
The new `nDCTransactionPolicyCreateBypassCurrent` policy makes this
behavior explicit: it does not call `SuppressBy`, does not transition
the workflow state, and does not create or update the current execution
record. `nDCTransactionPolicyCreateAsZombie` remains reserved for paths
that actually suppress an execution.
A genuinely brand-new workflow, or a completed/corrupted run with no
successor (the latest run), still becomes current as before.
<details>
<summary>Case 1: incoming snapshot is already Zombie</summary>
```text
SOURCE / RETIRED CELL | REPLICATION TARGET
------------------------------------|--------------------------------------
|
R1 [Zombie, non-current] | R1 absent
| cur -> none
|
FORCE-REPLICATE R1 SNAPSHOT |
state = Zombie ==R1==> | apply R1 (create path)
| currentRunID == ""
| |
| v
| BEFORE: CreateAsCurrent (BrandNew)
| -> rejected by persistence:
| "Invalid workflow create mode 0,
| state: Zombie"
|
| AFTER: CreateBypassCurrent
| -> R1 remains Zombie
| -> cur -> none
```
The snapshot is already Zombie before it reaches the transaction
manager. Promoting it to current would violate the Zombie/current
invariant; converting it is also unnecessary. The correct operation is
to preserve it as a non-current execution.
</details>
<details>
<summary>Case 2: closed orphan still points to a deleted
successor</summary>
```text
SOURCE | REPLICATION TARGET
------------------------------------|--------------------------------------
|
[1] R1 continue-as-new -> R2 |
R1 [completed, ->R2] | R1 absent
R2 running | R2 absent
cur -> R2 | cur -> none
|
[2] DELETE R2 (current run) |
R1 [completed, orphan, ->R2] |
R2 [deleted] |
cur -> none | cur -> none
|
[3] FORCE-REPLICATE R1 |
R1 [completed, ->R2] ==R1==> | apply R1 (create path)
| run absent, cur -> none
| |
| v
| BEFORE: CreateAsCurrent (BrandNew)
| -> cur -> R1
| [deleted lineage resurrected]
|
| AFTER: CreateBypassCurrent
| -> R1 remains Completed
| -> cur -> none
| [no resurrection]
```
Because R1 records R2 as its successor, R1 cannot be the lineage head
even though the target no longer has a current record. Bypass-current
preserves R1's history and state without promoting it.
</details>
## Why?
Force replication can reach the create path with a missing current
record in more than one form:
1. A migration/replication snapshot may already be `ZOMBIE`.
`CreateWorkflowModeBrandNew` rejects that state because a Zombie must
never own the current execution record.
2. A reset, continue-as-new, retry, or cron transition followed by
deletion of the successor can leave a completed/corrupted orphan that
still records its successor. Promoting that older run with
`CreateWorkflowModeBrandNew` resurrects a workflow whose lineage has
already moved on.
In both cases, the incoming run is known to be non-current. Persisting
it without changing its state and without writing a current record
preserves the replicated data while maintaining the current-execution
invariants.
## How did you test it?
- [x] built
- [ ] run locally and tested manually
- [ ] covered by existing tests
- [x] added new unit test(s)
- [ ] added new functional test(s)
Added unit coverage verifies that:
- a Zombie without successor metadata is persisted with
`CreateWorkflowModeBypassCurrent` and remains Zombie;
- Completed executions with `NewExecutionRunId` or `SuccessorRunId`
remain Completed;
- a Corrupted execution with a successor remains Corrupted; and
- the preserve-state path does not call suppression or update the
current execution record.
Validated with:
```text
go test ./service/history/ndc -run TestTransactionMgrForNewWorkflowSuite -count=1
```
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
512 lines
16 KiB
Go
512 lines
16 KiB
Go
//go:generate mockgen -package $GOPACKAGE -source $GOFILE -destination transaction_manager_mock.go
|
|
|
|
package ndc
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/google/uuid"
|
|
commonpb "go.temporal.io/api/common/v1"
|
|
historypb "go.temporal.io/api/history/v1"
|
|
"go.temporal.io/api/serviceerror"
|
|
enumsspb "go.temporal.io/server/api/enums/v1"
|
|
"go.temporal.io/server/chasm"
|
|
"go.temporal.io/server/common"
|
|
"go.temporal.io/server/common/cluster"
|
|
"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/common/persistence/serialization"
|
|
"go.temporal.io/server/common/persistence/versionhistory"
|
|
historyi "go.temporal.io/server/service/history/interfaces"
|
|
"go.temporal.io/server/service/history/workflow"
|
|
wcache "go.temporal.io/server/service/history/workflow/cache"
|
|
)
|
|
|
|
// NOTE: terminology
|
|
//
|
|
// 1. currentWorkflow means current running / closed workflow
|
|
// pointed by the current record in DB
|
|
//
|
|
// 2. targetWorkflow means the workflow to be replicated
|
|
// pointed by the replication task
|
|
//
|
|
// 3. newWorkflow means the workflow to be replicated, as part of continue as new
|
|
// pointed by the replication task
|
|
//
|
|
// 4. if target workflow and current workflow are the same
|
|
// then target workflow is set, current workflow is nil
|
|
//
|
|
// 5. suppress a workflow means turn a workflow into a zombie
|
|
// or terminate a workflow
|
|
|
|
// Cases to be handled by this file:
|
|
//
|
|
// create path (there will be only current branch)
|
|
// 1. create as current -> nDCTransactionPolicyCreateAsCurrent
|
|
// 2. create as zombie -> nDCTransactionPolicyCreateAsZombie
|
|
// 3. create without changing state or current -> nDCTransactionPolicyCreateBypassCurrent
|
|
//
|
|
// create path (there will be only current branch) + suppress current
|
|
// 1. create as current & suppress current -> nDCTransactionPolicySuppressCurrentAndCreateAsCurrent
|
|
//
|
|
// update to current branch path
|
|
// 1. update as current -> nDCTransactionPolicyUpdateAsCurrent
|
|
// 2. update as current & new created as current -> nDCTransactionPolicyUpdateAsCurrent
|
|
// 3. update as zombie -> nDCTransactionPolicyUpdateAsZombie
|
|
// 4. update as zombie & new created as zombie -> nDCTransactionPolicyUpdateAsZombie
|
|
//
|
|
// backfill to non current branch path
|
|
// 1. backfill as is
|
|
// 2. backfill as is & new created as zombie
|
|
//
|
|
// conflict resolve path
|
|
// 1. conflict resolve as current -> nDCTransactionPolicyConflictResolveAsCurrent
|
|
// 2. conflict resolve as zombie -> nDCTransactionPolicyConflictResolveAsZombie
|
|
//
|
|
// conflict resolve path + suppress current
|
|
// 1. update from zombie to current & suppress current -> nDCTransactionPolicySuppressCurrentAndUpdateAsCurrent
|
|
// 2. update from zombie to current & new created as current & suppress current -> nDCTransactionPolicySuppressCurrentAndUpdateAsCurrent
|
|
type nDCTransactionPolicy int
|
|
|
|
const (
|
|
nDCTransactionPolicyCreateAsCurrent nDCTransactionPolicy = iota
|
|
nDCTransactionPolicyCreateAsZombie
|
|
nDCTransactionPolicyCreateBypassCurrent
|
|
nDCTransactionPolicySuppressCurrentAndCreateAsCurrent
|
|
|
|
nDCTransactionPolicyUpdateAsCurrent
|
|
nDCTransactionPolicyUpdateAsZombie
|
|
|
|
nDCTransactionPolicyConflictResolveAsCurrent
|
|
nDCTransactionPolicyConflictResolveAsZombie
|
|
|
|
nDCTransactionPolicySuppressCurrentAndUpdateAsCurrent
|
|
)
|
|
|
|
const (
|
|
EventsReapplicationResetWorkflowReason = "events-reapplication"
|
|
)
|
|
|
|
type (
|
|
TransactionManager interface {
|
|
CreateWorkflow(
|
|
ctx context.Context,
|
|
archetypeID chasm.ArchetypeID,
|
|
targetWorkflow Workflow,
|
|
) error
|
|
UpdateWorkflow(
|
|
ctx context.Context,
|
|
isWorkflowRebuilt bool,
|
|
archetypeID chasm.ArchetypeID,
|
|
targetWorkflow Workflow,
|
|
newWorkflow Workflow,
|
|
) error
|
|
BackfillWorkflow(
|
|
ctx context.Context,
|
|
targetWorkflow Workflow,
|
|
targetWorkflowEventsSlice ...*persistence.WorkflowEvents,
|
|
) error
|
|
|
|
CheckWorkflowExists(
|
|
ctx context.Context,
|
|
namespaceID namespace.ID,
|
|
workflowID string,
|
|
runID string,
|
|
archetypeID chasm.ArchetypeID,
|
|
) (bool, error)
|
|
GetCurrentWorkflowRunID(
|
|
ctx context.Context,
|
|
namespaceID namespace.ID,
|
|
workflowID string,
|
|
archetypeID chasm.ArchetypeID,
|
|
) (string, error)
|
|
LoadWorkflow(
|
|
ctx context.Context,
|
|
namespaceID namespace.ID,
|
|
workflowID string,
|
|
runID string,
|
|
archetypeID chasm.ArchetypeID,
|
|
) (Workflow, error)
|
|
}
|
|
|
|
transactionMgrImpl struct {
|
|
shardContext historyi.ShardContext
|
|
namespaceRegistry namespace.Registry
|
|
workflowCache wcache.Cache
|
|
clusterMetadata cluster.Metadata
|
|
executionManager persistence.ExecutionManager
|
|
serializer serialization.Serializer
|
|
metricsHandler metrics.Handler
|
|
workflowResetter WorkflowResetter
|
|
eventsReapplier EventsReapplier
|
|
logger log.Logger
|
|
|
|
createMgr transactionMgrForNewWorkflow
|
|
updateMgr transactionMgrForExistingWorkflow
|
|
}
|
|
)
|
|
|
|
var _ TransactionManager = (*transactionMgrImpl)(nil)
|
|
|
|
func NewTransactionManager(
|
|
shardContext historyi.ShardContext,
|
|
workflowCache wcache.Cache,
|
|
eventsReapplier EventsReapplier,
|
|
logger log.Logger,
|
|
bypassVersionSemanticsCheck bool,
|
|
) *transactionMgrImpl {
|
|
|
|
transactionMgr := &transactionMgrImpl{
|
|
shardContext: shardContext,
|
|
namespaceRegistry: shardContext.GetNamespaceRegistry(),
|
|
workflowCache: workflowCache,
|
|
clusterMetadata: shardContext.GetClusterMetadata(),
|
|
executionManager: shardContext.GetExecutionManager(),
|
|
serializer: shardContext.GetPayloadSerializer(),
|
|
metricsHandler: shardContext.GetMetricsHandler(),
|
|
workflowResetter: NewWorkflowResetter(
|
|
shardContext,
|
|
workflowCache,
|
|
logger,
|
|
),
|
|
eventsReapplier: eventsReapplier,
|
|
logger: logger,
|
|
|
|
createMgr: nil,
|
|
updateMgr: nil,
|
|
}
|
|
taskRefresher := workflow.NewTaskRefresher(shardContext)
|
|
transactionMgr.createMgr = newTransactionMgrForNewWorkflow(shardContext, transactionMgr, bypassVersionSemanticsCheck, taskRefresher)
|
|
transactionMgr.updateMgr = newNDCTransactionMgrForExistingWorkflow(shardContext, transactionMgr, bypassVersionSemanticsCheck, taskRefresher)
|
|
return transactionMgr
|
|
}
|
|
|
|
func (r *transactionMgrImpl) CreateWorkflow(
|
|
ctx context.Context,
|
|
archetypeID chasm.ArchetypeID,
|
|
targetWorkflow Workflow,
|
|
) error {
|
|
|
|
return r.createMgr.dispatchForNewWorkflow(
|
|
ctx,
|
|
archetypeID,
|
|
targetWorkflow,
|
|
)
|
|
}
|
|
|
|
func (r *transactionMgrImpl) UpdateWorkflow(
|
|
ctx context.Context,
|
|
isWorkflowRebuilt bool,
|
|
archetypeID chasm.ArchetypeID,
|
|
targetWorkflow Workflow,
|
|
newWorkflow Workflow,
|
|
) error {
|
|
|
|
return r.updateMgr.dispatchForExistingWorkflow(
|
|
ctx,
|
|
isWorkflowRebuilt,
|
|
archetypeID,
|
|
targetWorkflow,
|
|
newWorkflow,
|
|
)
|
|
}
|
|
|
|
func (r *transactionMgrImpl) BackfillWorkflow(
|
|
ctx context.Context,
|
|
targetWorkflow Workflow,
|
|
targetWorkflowEventsSlice ...*persistence.WorkflowEvents,
|
|
) (retError error) {
|
|
|
|
defer func() {
|
|
if rec := recover(); rec != nil {
|
|
targetWorkflow.GetReleaseFn()(errPanic)
|
|
panic(rec)
|
|
} else {
|
|
targetWorkflow.GetReleaseFn()(retError)
|
|
}
|
|
}()
|
|
|
|
sizeSiff, err := targetWorkflow.GetContext().PersistWorkflowEvents(
|
|
ctx,
|
|
r.shardContext,
|
|
targetWorkflowEventsSlice...,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
targetWorkflow.GetMutableState().AddHistorySize(sizeSiff)
|
|
updateMode, transactionPolicy, err := r.backfillWorkflowEventsReapply(
|
|
ctx,
|
|
targetWorkflow,
|
|
targetWorkflowEventsSlice...,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return targetWorkflow.GetContext().UpdateWorkflowExecutionWithNew(
|
|
ctx,
|
|
r.shardContext,
|
|
updateMode,
|
|
nil,
|
|
nil,
|
|
transactionPolicy,
|
|
nil,
|
|
)
|
|
}
|
|
|
|
func (r *transactionMgrImpl) backfillWorkflowEventsReapply(
|
|
ctx context.Context,
|
|
targetWorkflow Workflow,
|
|
targetWorkflowEventsSlice ...*persistence.WorkflowEvents,
|
|
) (persistence.UpdateWorkflowMode, historyi.TransactionPolicy, error) {
|
|
|
|
isCurrentWorkflow, err := r.isWorkflowCurrent(ctx, chasm.WorkflowArchetypeID, targetWorkflow)
|
|
if err != nil {
|
|
return 0, historyi.TransactionPolicyActive, err
|
|
}
|
|
isWorkflowRunning := targetWorkflow.GetMutableState().IsWorkflowExecutionRunning()
|
|
targetWorkflowActiveCluster := targetWorkflow.GetMutableState().GetNamespaceEntry().ActiveClusterName(namespace.RoutingKey{ID: targetWorkflow.GetMutableState().GetExecutionInfo().WorkflowId})
|
|
currentCluster := r.clusterMetadata.GetCurrentClusterName()
|
|
isActiveCluster := targetWorkflowActiveCluster == currentCluster
|
|
|
|
// workflow events reapplication
|
|
// we need to handle 3 cases
|
|
// 1. target workflow is self & self being current & active
|
|
// a. workflow still running -> just reapply
|
|
// b. workflow closed -> reset current workflow & reapply
|
|
// 2. anything not case 1 -> find the current & active workflow to reapply
|
|
|
|
// case 1
|
|
if isCurrentWorkflow && isActiveCluster {
|
|
var totalEvents []*historypb.HistoryEvent
|
|
for _, events := range targetWorkflowEventsSlice {
|
|
totalEvents = append(totalEvents, events.Events...)
|
|
}
|
|
|
|
// case 1.a
|
|
if isWorkflowRunning {
|
|
if _, err := r.eventsReapplier.ReapplyEvents(
|
|
ctx,
|
|
targetWorkflow.GetMutableState(),
|
|
targetWorkflow.GetContext().UpdateRegistry(ctx),
|
|
totalEvents,
|
|
targetWorkflow.GetMutableState().GetExecutionState().GetRunId(),
|
|
); err != nil {
|
|
return 0, historyi.TransactionPolicyActive, err
|
|
}
|
|
return persistence.UpdateWorkflowModeUpdateCurrent, historyi.TransactionPolicyActive, nil
|
|
}
|
|
|
|
// case 1.b
|
|
// need to reset target workflow (which is also the current workflow)
|
|
// to accept events to be reapplied
|
|
baseMutableState := targetWorkflow.GetMutableState()
|
|
namespaceID := namespace.ID(baseMutableState.GetExecutionInfo().NamespaceId)
|
|
workflowID := baseMutableState.GetExecutionInfo().WorkflowId
|
|
baseRunID := baseMutableState.GetExecutionState().GetRunId()
|
|
resetRunID := uuid.NewString()
|
|
baseRebuildLastEventID := baseMutableState.GetLastCompletedWorkflowTaskStartedEventId()
|
|
if baseRebuildLastEventID == common.EmptyEventID {
|
|
// No completed workflow task. Pick the reset anchor by scenario:
|
|
// - real pending workflow task: anchor at its ScheduledEventID. The resetter
|
|
// rebuilds to that workflow task and fails it (synthesizing a started event if it
|
|
// has not started yet), so the scheduled event is a sufficient anchor whether or
|
|
// not the task already started.
|
|
// - transient (failing, attempt > 1) or speculative pending task: not a usable
|
|
// anchor - it has no persisted WorkflowTaskScheduled event (its ScheduledEventID
|
|
// is a not-yet-written NextEventID placeholder), so skip it.
|
|
// - no workflow task at all: nothing to anchor on.
|
|
if workflowTask := baseMutableState.GetPendingWorkflowTask(); workflowTask != nil &&
|
|
!baseMutableState.IsTransientWorkflowTask() &&
|
|
workflowTask.Type != enumsspb.WORKFLOW_TASK_TYPE_SPECULATIVE {
|
|
baseRebuildLastEventID = workflowTask.ScheduledEventID
|
|
}
|
|
}
|
|
|
|
baseVersionHistories := baseMutableState.GetExecutionInfo().GetVersionHistories()
|
|
baseCurrentVersionHistory, err := versionhistory.GetCurrentVersionHistory(baseVersionHistories)
|
|
if err != nil {
|
|
return 0, historyi.TransactionPolicyActive, err
|
|
}
|
|
baseRebuildLastEventVersion, err := versionhistory.GetVersionHistoryEventVersion(baseCurrentVersionHistory, baseRebuildLastEventID)
|
|
if err != nil {
|
|
return 0, historyi.TransactionPolicyActive, err
|
|
}
|
|
baseCurrentBranchToken := baseCurrentVersionHistory.GetBranchToken()
|
|
baseNextEventID := baseMutableState.GetNextEventID()
|
|
|
|
err = r.workflowResetter.ResetWorkflow(
|
|
ctx,
|
|
namespaceID,
|
|
workflowID,
|
|
baseRunID,
|
|
baseCurrentBranchToken,
|
|
baseRebuildLastEventID,
|
|
baseRebuildLastEventVersion,
|
|
baseNextEventID,
|
|
resetRunID,
|
|
targetWorkflow,
|
|
targetWorkflow,
|
|
EventsReapplicationResetWorkflowReason,
|
|
totalEvents,
|
|
nil,
|
|
false, // allowResetWithPendingChildren
|
|
nil,
|
|
)
|
|
switch err.(type) {
|
|
case *serviceerror.InvalidArgument:
|
|
// no-op. Usually this is due to reset workflow with pending child workflows
|
|
r.logger.Warn("Cannot reset workflow. Ignoring reapply events.", tag.Error(err))
|
|
// the target workflow is not reset so it is still the current workflow. It need to persist updated version histories.
|
|
return persistence.UpdateWorkflowModeUpdateCurrent, historyi.TransactionPolicyPassive, nil
|
|
case nil:
|
|
// after the reset of target workflow (current workflow) with additional events to be reapplied
|
|
// target workflow is no longer the current workflow
|
|
return persistence.UpdateWorkflowModeBypassCurrent, historyi.TransactionPolicyPassive, nil
|
|
default:
|
|
return 0, historyi.TransactionPolicyActive, err
|
|
}
|
|
}
|
|
|
|
// case 2
|
|
// find the current & active workflow to reapply
|
|
if err := targetWorkflow.GetContext().ReapplyEvents(
|
|
ctx,
|
|
r.shardContext,
|
|
targetWorkflowEventsSlice,
|
|
); err != nil {
|
|
return 0, historyi.TransactionPolicyActive, err
|
|
}
|
|
|
|
if isCurrentWorkflow {
|
|
return persistence.UpdateWorkflowModeUpdateCurrent, historyi.TransactionPolicyPassive, nil
|
|
}
|
|
return persistence.UpdateWorkflowModeBypassCurrent, historyi.TransactionPolicyPassive, nil
|
|
}
|
|
|
|
func (r *transactionMgrImpl) CheckWorkflowExists(
|
|
ctx context.Context,
|
|
namespaceID namespace.ID,
|
|
workflowID string,
|
|
runID string,
|
|
archetypeID chasm.ArchetypeID,
|
|
) (bool, error) {
|
|
|
|
_, err := r.shardContext.GetWorkflowExecution(
|
|
ctx,
|
|
&persistence.GetWorkflowExecutionRequest{
|
|
ShardID: r.shardContext.GetShardID(),
|
|
NamespaceID: namespaceID.String(),
|
|
WorkflowID: workflowID,
|
|
RunID: runID,
|
|
ArchetypeID: archetypeID,
|
|
},
|
|
)
|
|
|
|
switch err.(type) {
|
|
case nil:
|
|
return true, nil
|
|
case *serviceerror.NotFound:
|
|
return false, nil
|
|
default:
|
|
return false, err
|
|
}
|
|
}
|
|
|
|
func (r *transactionMgrImpl) GetCurrentWorkflowRunID(
|
|
ctx context.Context,
|
|
namespaceID namespace.ID,
|
|
workflowID string,
|
|
archetypeID chasm.ArchetypeID,
|
|
) (string, error) {
|
|
|
|
resp, err := r.shardContext.GetCurrentExecution(
|
|
ctx,
|
|
&persistence.GetCurrentExecutionRequest{
|
|
ShardID: r.shardContext.GetShardID(),
|
|
NamespaceID: namespaceID.String(),
|
|
WorkflowID: workflowID,
|
|
ArchetypeID: archetypeID,
|
|
},
|
|
)
|
|
|
|
switch err.(type) {
|
|
case nil:
|
|
return resp.RunID, nil
|
|
case *serviceerror.NotFound:
|
|
return "", nil
|
|
default:
|
|
return "", err
|
|
}
|
|
}
|
|
|
|
func (r *transactionMgrImpl) LoadWorkflow(
|
|
ctx context.Context,
|
|
namespaceID namespace.ID,
|
|
workflowID string,
|
|
runID string,
|
|
archetypeID chasm.ArchetypeID,
|
|
) (Workflow, error) {
|
|
|
|
weContext, release, err := r.workflowCache.GetOrCreateChasmExecution(
|
|
ctx,
|
|
r.shardContext,
|
|
namespaceID,
|
|
&commonpb.WorkflowExecution{
|
|
WorkflowId: workflowID,
|
|
RunId: runID,
|
|
},
|
|
archetypeID,
|
|
locks.PriorityHigh,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ms, err := weContext.LoadMutableState(ctx, r.shardContext)
|
|
if err != nil {
|
|
// no matter what error happen, we need to retry
|
|
release(err)
|
|
return nil, err
|
|
}
|
|
return NewWorkflow(r.clusterMetadata, weContext, ms, release), nil
|
|
}
|
|
|
|
func (r *transactionMgrImpl) isWorkflowCurrent(
|
|
ctx context.Context,
|
|
archetypeID chasm.ArchetypeID,
|
|
targetWorkflow Workflow,
|
|
) (bool, error) {
|
|
|
|
// since we are not rebuilding the mutable state (when doing back fill) then we
|
|
// can trust the result from IsCurrentWorkflowGuaranteed
|
|
if targetWorkflow.GetMutableState().IsCurrentWorkflowGuaranteed() {
|
|
return true, nil
|
|
}
|
|
|
|
// target workflow is not guaranteed to be current workflow, do additional check
|
|
executionInfo := targetWorkflow.GetMutableState().GetExecutionInfo()
|
|
executionState := targetWorkflow.GetMutableState().GetExecutionState()
|
|
namespaceID := namespace.ID(executionInfo.NamespaceId)
|
|
workflowID := executionInfo.WorkflowId
|
|
runID := executionState.RunId
|
|
|
|
currentRunID, err := r.GetCurrentWorkflowRunID(
|
|
ctx,
|
|
namespaceID,
|
|
workflowID,
|
|
archetypeID,
|
|
)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
return currentRunID == runID, nil
|
|
}
|