mirror of
https://github.com/hanzoai/tasks.git
synced 2026-10-04 02:04:03 +00:00
699 lines
22 KiB
Go
699 lines
22 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 history
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"time"
|
|
|
|
commonpb "go.temporal.io/api/common/v1"
|
|
enumspb "go.temporal.io/api/enums/v1"
|
|
"go.temporal.io/api/serviceerror"
|
|
taskqueuepb "go.temporal.io/api/taskqueue/v1"
|
|
|
|
"go.temporal.io/server/api/historyservice/v1"
|
|
"go.temporal.io/server/api/matchingservice/v1"
|
|
"go.temporal.io/server/common"
|
|
"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/primitives/timestamp"
|
|
"go.temporal.io/server/common/xdc"
|
|
"go.temporal.io/server/service/history/consts"
|
|
"go.temporal.io/server/service/history/ndc"
|
|
"go.temporal.io/server/service/history/queues"
|
|
"go.temporal.io/server/service/history/shard"
|
|
"go.temporal.io/server/service/history/tasks"
|
|
"go.temporal.io/server/service/history/workflow"
|
|
wcache "go.temporal.io/server/service/history/workflow/cache"
|
|
"go.temporal.io/server/service/worker/archiver"
|
|
)
|
|
|
|
type (
|
|
transferQueueStandbyTaskExecutor struct {
|
|
*transferQueueTaskExecutorBase
|
|
|
|
clusterName string
|
|
nDCHistoryResender xdc.NDCHistoryResender
|
|
}
|
|
)
|
|
|
|
var (
|
|
errVerificationFailed = errors.New("failed to verify target workflow state")
|
|
)
|
|
|
|
func newTransferQueueStandbyTaskExecutor(
|
|
shard shard.Context,
|
|
workflowCache wcache.Cache,
|
|
archivalClient archiver.Client,
|
|
nDCHistoryResender xdc.NDCHistoryResender,
|
|
logger log.Logger,
|
|
metricProvider metrics.Handler,
|
|
clusterName string,
|
|
matchingClient matchingservice.MatchingServiceClient,
|
|
) queues.Executor {
|
|
return &transferQueueStandbyTaskExecutor{
|
|
transferQueueTaskExecutorBase: newTransferQueueTaskExecutorBase(
|
|
shard,
|
|
workflowCache,
|
|
archivalClient,
|
|
logger,
|
|
metricProvider,
|
|
matchingClient,
|
|
),
|
|
clusterName: clusterName,
|
|
nDCHistoryResender: nDCHistoryResender,
|
|
}
|
|
}
|
|
|
|
func (t *transferQueueStandbyTaskExecutor) Execute(
|
|
ctx context.Context,
|
|
executable queues.Executable,
|
|
) ([]metrics.Tag, bool, error) {
|
|
task := executable.GetTask()
|
|
taskType := queues.GetStandbyTransferTaskTypeTagValue(task)
|
|
metricsTags := []metrics.Tag{
|
|
getNamespaceTagByID(t.shard.GetNamespaceRegistry(), task.GetNamespaceID()),
|
|
metrics.TaskTypeTag(taskType),
|
|
metrics.OperationTag(taskType), // for backward compatibility
|
|
}
|
|
|
|
var err error
|
|
switch task := task.(type) {
|
|
case *tasks.ActivityTask:
|
|
err = t.processActivityTask(ctx, task)
|
|
case *tasks.WorkflowTask:
|
|
err = t.processWorkflowTask(ctx, task)
|
|
case *tasks.CancelExecutionTask:
|
|
err = t.processCancelExecution(ctx, task)
|
|
case *tasks.SignalExecutionTask:
|
|
err = t.processSignalExecution(ctx, task)
|
|
case *tasks.StartChildExecutionTask:
|
|
err = t.processStartChildExecution(ctx, task)
|
|
case *tasks.ResetWorkflowTask:
|
|
// no reset needed for standby
|
|
// TODO: add error logs
|
|
err = nil
|
|
case *tasks.CloseExecutionTask:
|
|
err = t.processCloseExecution(ctx, task)
|
|
case *tasks.DeleteExecutionTask:
|
|
err = t.processDeleteExecutionTask(ctx, task, false)
|
|
default:
|
|
err = errUnknownTransferTask
|
|
}
|
|
|
|
return metricsTags, false, err
|
|
}
|
|
|
|
func (t *transferQueueStandbyTaskExecutor) processActivityTask(
|
|
ctx context.Context,
|
|
transferTask *tasks.ActivityTask,
|
|
) error {
|
|
processTaskIfClosed := false
|
|
actionFn := func(_ context.Context, wfContext workflow.Context, mutableState workflow.MutableState) (interface{}, error) {
|
|
activityInfo, ok := mutableState.GetActivityInfo(transferTask.ScheduledEventID)
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
|
|
err := CheckTaskVersion(t.shard, t.logger, mutableState.GetNamespaceEntry(), activityInfo.Version, transferTask.Version, transferTask)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if activityInfo.StartedEventId == common.EmptyEventID {
|
|
return newActivityTaskPostActionInfo(mutableState, *activityInfo.ScheduleToStartTimeout)
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
return t.processTransfer(
|
|
ctx,
|
|
processTaskIfClosed,
|
|
transferTask,
|
|
actionFn,
|
|
getStandbyPostActionFn(
|
|
transferTask,
|
|
t.getCurrentTime,
|
|
t.config.StandbyTaskMissingEventsResendDelay(transferTask.GetType()),
|
|
t.config.StandbyTaskMissingEventsDiscardDelay(transferTask.GetType()),
|
|
t.fetchHistoryFromRemote,
|
|
t.pushActivity,
|
|
),
|
|
)
|
|
}
|
|
|
|
func (t *transferQueueStandbyTaskExecutor) processWorkflowTask(
|
|
ctx context.Context,
|
|
transferTask *tasks.WorkflowTask,
|
|
) error {
|
|
processTaskIfClosed := false
|
|
actionFn := func(_ context.Context, wfContext workflow.Context, mutableState workflow.MutableState) (interface{}, error) {
|
|
wtInfo, ok := mutableState.GetWorkflowTaskInfo(transferTask.ScheduledEventID)
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
|
|
executionInfo := mutableState.GetExecutionInfo()
|
|
|
|
taskQueue := &taskqueuepb.TaskQueue{
|
|
// at standby, always use original task queue, disregards the task.TaskQueue which could be sticky
|
|
Name: mutableState.GetExecutionInfo().TaskQueue,
|
|
Kind: enumspb.TASK_QUEUE_KIND_NORMAL,
|
|
}
|
|
workflowRunTimeout := timestamp.DurationValue(executionInfo.WorkflowRunTimeout)
|
|
taskScheduleToStartTimeoutSeconds := int64(workflowRunTimeout.Round(time.Second).Seconds())
|
|
if mutableState.GetExecutionInfo().TaskQueue != transferTask.TaskQueue {
|
|
// Experimental: try to push sticky task as regular task with sticky timeout as TTL.
|
|
// workflow might be sticky before namespace become standby
|
|
// there shall already be a schedule_to_start timer created
|
|
taskScheduleToStartTimeoutSeconds = int64(timestamp.DurationValue(executionInfo.StickyScheduleToStartTimeout).Seconds())
|
|
}
|
|
err := CheckTaskVersion(t.shard, t.logger, mutableState.GetNamespaceEntry(), wtInfo.Version, transferTask.Version, transferTask)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if wtInfo.StartedEventID == common.EmptyEventID {
|
|
return newWorkflowTaskPostActionInfo(
|
|
mutableState,
|
|
taskScheduleToStartTimeoutSeconds,
|
|
*taskQueue,
|
|
)
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
return t.processTransfer(
|
|
ctx,
|
|
processTaskIfClosed,
|
|
transferTask,
|
|
actionFn,
|
|
getStandbyPostActionFn(
|
|
transferTask,
|
|
t.getCurrentTime,
|
|
t.config.StandbyTaskMissingEventsResendDelay(transferTask.GetType()),
|
|
t.config.StandbyTaskMissingEventsDiscardDelay(transferTask.GetType()),
|
|
t.fetchHistoryFromRemote,
|
|
t.pushWorkflowTask,
|
|
),
|
|
)
|
|
}
|
|
|
|
func (t *transferQueueStandbyTaskExecutor) processCloseExecution(
|
|
ctx context.Context,
|
|
transferTask *tasks.CloseExecutionTask,
|
|
) error {
|
|
processTaskIfClosed := true
|
|
actionFn := func(ctx context.Context, wfContext workflow.Context, mutableState workflow.MutableState) (interface{}, error) {
|
|
if mutableState.IsWorkflowExecutionRunning() {
|
|
// this can happen if workflow is reset.
|
|
return nil, nil
|
|
}
|
|
|
|
wfCloseTime, err := mutableState.GetWorkflowCloseTime(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
executionInfo := mutableState.GetExecutionInfo()
|
|
executionState := mutableState.GetExecutionState()
|
|
workflowTypeName := executionInfo.WorkflowTypeName
|
|
workflowStatus := executionState.Status
|
|
workflowHistoryLength := mutableState.GetNextEventID() - 1
|
|
workflowStartTime := timestamp.TimeValue(mutableState.GetExecutionInfo().GetStartTime())
|
|
workflowExecutionTime := timestamp.TimeValue(mutableState.GetExecutionInfo().GetExecutionTime())
|
|
visibilityMemo := getWorkflowMemo(executionInfo.Memo)
|
|
searchAttr := getSearchAttributes(executionInfo.SearchAttributes)
|
|
|
|
lastWriteVersion, err := mutableState.GetLastWriteVersion()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = CheckTaskVersion(t.shard, t.logger, mutableState.GetNamespaceEntry(), lastWriteVersion, transferTask.Version, transferTask)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !transferTask.CanSkipVisibilityArchival {
|
|
if err := t.archiveVisibility(
|
|
ctx,
|
|
namespace.ID(transferTask.NamespaceID),
|
|
transferTask.WorkflowID,
|
|
transferTask.RunID,
|
|
workflowTypeName,
|
|
workflowStartTime,
|
|
workflowExecutionTime,
|
|
timestamp.TimeValue(wfCloseTime),
|
|
workflowStatus,
|
|
workflowHistoryLength,
|
|
visibilityMemo,
|
|
searchAttr,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// verify if parent got the completion event
|
|
verifyCompletionRecorded := mutableState.HasParentExecution() && executionInfo.NewExecutionRunId == ""
|
|
if verifyCompletionRecorded {
|
|
// load close event only if needed.
|
|
completionEvent, err := mutableState.GetCompletionEvent(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
verifyCompletionRecorded = verifyCompletionRecorded && !ndc.IsTerminatedByResetter(completionEvent)
|
|
}
|
|
|
|
if verifyCompletionRecorded {
|
|
_, err := t.historyClient.VerifyChildExecutionCompletionRecorded(ctx, &historyservice.VerifyChildExecutionCompletionRecordedRequest{
|
|
NamespaceId: executionInfo.ParentNamespaceId,
|
|
ParentExecution: &commonpb.WorkflowExecution{
|
|
WorkflowId: executionInfo.ParentWorkflowId,
|
|
RunId: executionInfo.ParentRunId,
|
|
},
|
|
ChildExecution: &commonpb.WorkflowExecution{
|
|
WorkflowId: transferTask.WorkflowID,
|
|
RunId: transferTask.RunID,
|
|
},
|
|
ParentInitiatedId: executionInfo.ParentInitiatedId,
|
|
ParentInitiatedVersion: executionInfo.ParentInitiatedVersion,
|
|
Clock: executionInfo.ParentClock,
|
|
})
|
|
switch err.(type) {
|
|
case nil, *serviceerror.NamespaceNotFound, *serviceerror.Unimplemented:
|
|
return nil, nil
|
|
case *serviceerror.NotFound, *serviceerror.WorkflowNotReady:
|
|
return verifyChildCompletionRecordedInfo, nil
|
|
default:
|
|
t.logger.Error("Failed to verify child execution completion recoreded",
|
|
tag.WorkflowNamespaceID(transferTask.GetNamespaceID()),
|
|
tag.WorkflowID(transferTask.GetWorkflowID()),
|
|
tag.WorkflowRunID(transferTask.GetRunID()),
|
|
tag.Error(err),
|
|
)
|
|
|
|
// NOTE: we do not return the error here which will cause the mutable state to be cleared and reloaded upon retry
|
|
// it's unnecessary as the error is in the target workflow, not this workflow.
|
|
return nil, errVerificationFailed
|
|
}
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
return t.processTransfer(
|
|
ctx,
|
|
processTaskIfClosed,
|
|
transferTask,
|
|
actionFn,
|
|
getStandbyPostActionFn(
|
|
transferTask,
|
|
t.getCurrentTime,
|
|
t.config.StandbyTaskMissingEventsResendDelay(transferTask.GetType()),
|
|
t.config.StandbyTaskMissingEventsDiscardDelay(transferTask.GetType()),
|
|
standbyTaskPostActionNoOp,
|
|
standbyTransferTaskPostActionTaskDiscarded,
|
|
),
|
|
)
|
|
}
|
|
|
|
func (t *transferQueueStandbyTaskExecutor) processCancelExecution(
|
|
ctx context.Context,
|
|
transferTask *tasks.CancelExecutionTask,
|
|
) error {
|
|
processTaskIfClosed := false
|
|
actionFn := func(_ context.Context, wfContext workflow.Context, mutableState workflow.MutableState) (interface{}, error) {
|
|
requestCancelInfo, ok := mutableState.GetRequestCancelInfo(transferTask.InitiatedEventID)
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
|
|
err := CheckTaskVersion(t.shard, t.logger, mutableState.GetNamespaceEntry(), requestCancelInfo.Version, transferTask.Version, transferTask)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return getHistoryResendInfo(mutableState)
|
|
}
|
|
|
|
return t.processTransfer(
|
|
ctx,
|
|
processTaskIfClosed,
|
|
transferTask,
|
|
actionFn,
|
|
getStandbyPostActionFn(
|
|
transferTask,
|
|
t.getCurrentTime,
|
|
t.config.StandbyTaskMissingEventsResendDelay(transferTask.GetType()),
|
|
t.config.StandbyTaskMissingEventsDiscardDelay(transferTask.GetType()),
|
|
t.fetchHistoryFromRemote,
|
|
standbyTransferTaskPostActionTaskDiscarded,
|
|
),
|
|
)
|
|
}
|
|
|
|
func (t *transferQueueStandbyTaskExecutor) processSignalExecution(
|
|
ctx context.Context,
|
|
transferTask *tasks.SignalExecutionTask,
|
|
) error {
|
|
processTaskIfClosed := false
|
|
actionFn := func(_ context.Context, wfContext workflow.Context, mutableState workflow.MutableState) (interface{}, error) {
|
|
signalInfo, ok := mutableState.GetSignalInfo(transferTask.InitiatedEventID)
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
|
|
err := CheckTaskVersion(t.shard, t.logger, mutableState.GetNamespaceEntry(), signalInfo.Version, transferTask.Version, transferTask)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return getHistoryResendInfo(mutableState)
|
|
}
|
|
|
|
return t.processTransfer(
|
|
ctx,
|
|
processTaskIfClosed,
|
|
transferTask,
|
|
actionFn,
|
|
getStandbyPostActionFn(
|
|
transferTask,
|
|
t.getCurrentTime,
|
|
t.config.StandbyTaskMissingEventsResendDelay(transferTask.GetType()),
|
|
t.config.StandbyTaskMissingEventsDiscardDelay(transferTask.GetType()),
|
|
t.fetchHistoryFromRemote,
|
|
standbyTransferTaskPostActionTaskDiscarded,
|
|
),
|
|
)
|
|
}
|
|
|
|
func (t *transferQueueStandbyTaskExecutor) processStartChildExecution(
|
|
ctx context.Context,
|
|
transferTask *tasks.StartChildExecutionTask,
|
|
) error {
|
|
processTaskIfClosed := true
|
|
actionFn := func(ctx context.Context, wfContext workflow.Context, mutableState workflow.MutableState) (interface{}, error) {
|
|
childWorkflowInfo, ok := mutableState.GetChildExecutionInfo(transferTask.InitiatedEventID)
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
|
|
err := CheckTaskVersion(t.shard, t.logger, mutableState.GetNamespaceEntry(), childWorkflowInfo.Version, transferTask.Version, transferTask)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
workflowClosed := !mutableState.IsWorkflowExecutionRunning()
|
|
childStarted := childWorkflowInfo.StartedEventId != common.EmptyEventID
|
|
childAbandon := childWorkflowInfo.ParentClosePolicy == enumspb.PARENT_CLOSE_POLICY_ABANDON
|
|
|
|
if workflowClosed && !(childStarted && childAbandon) {
|
|
// NOTE: ideally for workflowClosed, child not started, parent close policy is abandon case,
|
|
// we should continue to start the child workflow in active cluster, so standby logic also need to
|
|
// perform the verification. However, we can't do that due to some technial reasons.
|
|
// Please check the comments in processStartChildExecution in transferQueueActiveTaskExecutor.go
|
|
// for details.
|
|
return nil, nil
|
|
}
|
|
|
|
if !childStarted {
|
|
historyResendInfo, err := getHistoryResendInfo(mutableState)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &startChildExecutionPostActionInfo{
|
|
historyResendInfo: historyResendInfo,
|
|
}, nil
|
|
}
|
|
|
|
_, err = t.historyClient.VerifyFirstWorkflowTaskScheduled(ctx, &historyservice.VerifyFirstWorkflowTaskScheduledRequest{
|
|
NamespaceId: transferTask.TargetNamespaceID,
|
|
WorkflowExecution: &commonpb.WorkflowExecution{
|
|
WorkflowId: childWorkflowInfo.StartedWorkflowId,
|
|
RunId: childWorkflowInfo.StartedRunId,
|
|
},
|
|
Clock: childWorkflowInfo.Clock,
|
|
})
|
|
switch err.(type) {
|
|
case nil, *serviceerror.NamespaceNotFound, *serviceerror.Unimplemented:
|
|
return nil, nil
|
|
case *serviceerror.NotFound, *serviceerror.WorkflowNotReady:
|
|
return &startChildExecutionPostActionInfo{}, nil
|
|
default:
|
|
t.logger.Error("Failed to verify first workflow task scheduled",
|
|
tag.WorkflowNamespaceID(transferTask.GetNamespaceID()),
|
|
tag.WorkflowID(transferTask.GetWorkflowID()),
|
|
tag.WorkflowRunID(transferTask.GetRunID()),
|
|
tag.Error(err),
|
|
)
|
|
|
|
// NOTE: we do not return the error here which will cause the mutable state to be cleared and reloaded upon retry
|
|
// it's unnecessary as the error is in the target workflow, not this workflow.
|
|
return nil, errVerificationFailed
|
|
}
|
|
}
|
|
|
|
return t.processTransfer(
|
|
ctx,
|
|
processTaskIfClosed,
|
|
transferTask,
|
|
actionFn,
|
|
getStandbyPostActionFn(
|
|
transferTask,
|
|
t.getCurrentTime,
|
|
t.config.StandbyTaskMissingEventsResendDelay(transferTask.GetType()),
|
|
t.config.StandbyTaskMissingEventsDiscardDelay(transferTask.GetType()),
|
|
t.startChildExecutionResendPostAction,
|
|
standbyTransferTaskPostActionTaskDiscarded,
|
|
),
|
|
)
|
|
}
|
|
|
|
func (t *transferQueueStandbyTaskExecutor) processTransfer(
|
|
ctx context.Context,
|
|
processTaskIfClosed bool,
|
|
taskInfo tasks.Task,
|
|
actionFn standbyActionFn,
|
|
postActionFn standbyPostActionFn,
|
|
) (retError error) {
|
|
ctx, cancel := context.WithTimeout(ctx, taskTimeout)
|
|
defer cancel()
|
|
|
|
weContext, release, err := getWorkflowExecutionContextForTask(ctx, t.cache, taskInfo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() {
|
|
if retError == consts.ErrTaskRetry || retError == errVerificationFailed {
|
|
release(nil)
|
|
} else {
|
|
release(retError)
|
|
}
|
|
}()
|
|
|
|
mutableState, err := loadMutableStateForTransferTask(ctx, weContext, taskInfo, t.metricHandler, t.logger)
|
|
if err != nil || mutableState == nil {
|
|
return err
|
|
}
|
|
|
|
if !mutableState.IsWorkflowExecutionRunning() && !processTaskIfClosed {
|
|
// workflow already finished, no need to process transfer task.
|
|
return nil
|
|
}
|
|
|
|
historyResendInfo, err := actionFn(ctx, weContext, mutableState)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// NOTE: do not access anything related mutable state after this lock release
|
|
release(nil)
|
|
return postActionFn(ctx, taskInfo, historyResendInfo, t.logger)
|
|
}
|
|
|
|
func (t *transferQueueStandbyTaskExecutor) pushActivity(
|
|
ctx context.Context,
|
|
task tasks.Task,
|
|
postActionInfo interface{},
|
|
logger log.Logger,
|
|
) error {
|
|
if postActionInfo == nil {
|
|
return nil
|
|
}
|
|
|
|
pushActivityInfo := postActionInfo.(*activityTaskPostActionInfo)
|
|
timeout := pushActivityInfo.activityTaskScheduleToStartTimeout
|
|
return t.transferQueueTaskExecutorBase.pushActivity(
|
|
ctx,
|
|
task.(*tasks.ActivityTask),
|
|
&timeout,
|
|
)
|
|
}
|
|
|
|
func (t *transferQueueStandbyTaskExecutor) pushWorkflowTask(
|
|
ctx context.Context,
|
|
task tasks.Task,
|
|
postActionInfo interface{},
|
|
logger log.Logger,
|
|
) error {
|
|
if postActionInfo == nil {
|
|
return nil
|
|
}
|
|
|
|
pushwtInfo := postActionInfo.(*workflowTaskPostActionInfo)
|
|
timeout := pushwtInfo.workflowTaskScheduleToStartTimeout
|
|
return t.transferQueueTaskExecutorBase.pushWorkflowTask(
|
|
ctx,
|
|
task.(*tasks.WorkflowTask),
|
|
&pushwtInfo.taskqueue,
|
|
timestamp.DurationFromSeconds(timeout),
|
|
)
|
|
}
|
|
|
|
func (t *transferQueueStandbyTaskExecutor) startChildExecutionResendPostAction(
|
|
ctx context.Context,
|
|
taskInfo tasks.Task,
|
|
postActionInfo interface{},
|
|
log log.Logger,
|
|
) error {
|
|
if postActionInfo == nil {
|
|
return nil
|
|
}
|
|
|
|
historyResendInfo := postActionInfo.(*startChildExecutionPostActionInfo).historyResendInfo
|
|
if historyResendInfo != nil {
|
|
return t.fetchHistoryFromRemote(ctx, taskInfo, historyResendInfo, log)
|
|
}
|
|
|
|
return standbyTaskPostActionNoOp(ctx, taskInfo, postActionInfo, log)
|
|
}
|
|
|
|
func (t *transferQueueStandbyTaskExecutor) fetchHistoryFromRemote(
|
|
ctx context.Context,
|
|
taskInfo tasks.Task,
|
|
postActionInfo interface{},
|
|
logger log.Logger,
|
|
) error {
|
|
var resendInfo *historyResendInfo
|
|
switch postActionInfo := postActionInfo.(type) {
|
|
case nil:
|
|
return nil
|
|
case *historyResendInfo:
|
|
resendInfo = postActionInfo
|
|
case *activityTaskPostActionInfo:
|
|
resendInfo = postActionInfo.historyResendInfo
|
|
case *workflowTaskPostActionInfo:
|
|
resendInfo = postActionInfo.historyResendInfo
|
|
default:
|
|
logger.Fatal("unknown post action info for fetching remote history", tag.Value(postActionInfo))
|
|
}
|
|
|
|
remoteClusterName, err := getRemoteClusterName(
|
|
t.currentClusterName,
|
|
t.registry,
|
|
taskInfo.GetNamespaceID(),
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
scope := t.metricHandler.WithTags(metrics.OperationTag(metrics.HistoryRereplicationByTransferTaskScope))
|
|
scope.Counter(metrics.ClientRequests.GetMetricName()).Record(1)
|
|
startTime := time.Now().UTC()
|
|
defer func() { scope.Timer(metrics.ClientLatency.GetMetricName()).Record(time.Since(startTime)) }()
|
|
|
|
adminClient, err := t.shard.GetRemoteAdminClient(remoteClusterName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if resendInfo.lastEventID == common.EmptyEventID || resendInfo.lastEventVersion == common.EmptyVersion {
|
|
t.logger.Error("Error re-replicating history from remote: transferQueueStandbyProcessor encountered empty historyResendInfo.",
|
|
tag.ShardID(t.shard.GetShardID()),
|
|
tag.WorkflowNamespaceID(taskInfo.GetNamespaceID()),
|
|
tag.WorkflowID(taskInfo.GetWorkflowID()),
|
|
tag.WorkflowRunID(taskInfo.GetRunID()),
|
|
tag.SourceCluster(remoteClusterName))
|
|
|
|
return consts.ErrTaskRetry
|
|
}
|
|
|
|
if err = refreshTasks(
|
|
ctx,
|
|
adminClient,
|
|
namespace.ID(taskInfo.GetNamespaceID()),
|
|
taskInfo.GetWorkflowID(),
|
|
taskInfo.GetRunID(),
|
|
); err != nil {
|
|
if _, isNotFound := err.(*serviceerror.NamespaceNotFound); isNotFound {
|
|
// Don't log NamespaceNotFound error because it is valid case, and return error to stop retrying.
|
|
return err
|
|
}
|
|
t.logger.Error("Error refresh tasks from remote.",
|
|
tag.ShardID(t.shard.GetShardID()),
|
|
tag.WorkflowNamespaceID(taskInfo.GetNamespaceID()),
|
|
tag.WorkflowID(taskInfo.GetWorkflowID()),
|
|
tag.WorkflowRunID(taskInfo.GetRunID()),
|
|
tag.ClusterName(remoteClusterName),
|
|
tag.Error(err))
|
|
}
|
|
|
|
// NOTE: history resend may take long time and its timeout is currently
|
|
// controlled by a separate dynamicconfig config: StandbyTaskReReplicationContextTimeout
|
|
if err = t.nDCHistoryResender.SendSingleWorkflowHistory(
|
|
ctx,
|
|
remoteClusterName,
|
|
namespace.ID(taskInfo.GetNamespaceID()),
|
|
taskInfo.GetWorkflowID(),
|
|
taskInfo.GetRunID(),
|
|
resendInfo.lastEventID,
|
|
resendInfo.lastEventVersion,
|
|
0,
|
|
0,
|
|
); err != nil {
|
|
if _, isNotFound := err.(*serviceerror.NamespaceNotFound); isNotFound {
|
|
// Don't log NamespaceNotFound error because it is valid case, and return error to stop retrying.
|
|
return err
|
|
}
|
|
t.logger.Error("Error re-replicating history from remote.",
|
|
tag.ShardID(t.shard.GetShardID()),
|
|
tag.WorkflowNamespaceID(taskInfo.GetNamespaceID()),
|
|
tag.WorkflowID(taskInfo.GetWorkflowID()),
|
|
tag.WorkflowRunID(taskInfo.GetRunID()),
|
|
tag.SourceCluster(remoteClusterName),
|
|
tag.Error(err))
|
|
}
|
|
|
|
// Return retryable error, so task processing will retry.
|
|
return consts.ErrTaskRetry
|
|
}
|
|
|
|
func (t *transferQueueStandbyTaskExecutor) getCurrentTime() time.Time {
|
|
return t.shard.GetCurrentTime(t.clusterName)
|
|
}
|