mirror of
https://github.com/hanzoai/tasks.git
synced 2026-10-04 02:04:03 +00:00
563 lines
16 KiB
Go
563 lines
16 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"
|
|
"time"
|
|
|
|
enumspb "go.temporal.io/api/enums/v1"
|
|
"go.temporal.io/api/serviceerror"
|
|
taskqueuepb "go.temporal.io/api/taskqueue/v1"
|
|
|
|
"go.temporal.io/server/api/adminservice/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/shard"
|
|
"go.temporal.io/server/service/history/tasks"
|
|
"go.temporal.io/server/service/history/workflow"
|
|
"go.temporal.io/server/service/worker/archiver"
|
|
)
|
|
|
|
type (
|
|
transferQueueStandbyTaskExecutor struct {
|
|
*transferQueueTaskExecutorBase
|
|
|
|
clusterName string
|
|
adminClient adminservice.AdminServiceClient
|
|
nDCHistoryResender xdc.NDCHistoryResender
|
|
}
|
|
)
|
|
|
|
func newTransferQueueStandbyTaskExecutor(
|
|
shard shard.Context,
|
|
workflowCache workflow.Cache,
|
|
archivalClient archiver.Client,
|
|
nDCHistoryResender xdc.NDCHistoryResender,
|
|
logger log.Logger,
|
|
clusterName string,
|
|
matchingClient matchingservice.MatchingServiceClient,
|
|
) queueTaskExecutor {
|
|
return &transferQueueStandbyTaskExecutor{
|
|
transferQueueTaskExecutorBase: newTransferQueueTaskExecutorBase(
|
|
shard,
|
|
workflowCache,
|
|
archivalClient,
|
|
logger,
|
|
matchingClient,
|
|
),
|
|
clusterName: clusterName,
|
|
adminClient: shard.GetRemoteAdminClient(clusterName),
|
|
nDCHistoryResender: nDCHistoryResender,
|
|
}
|
|
}
|
|
|
|
func (t *transferQueueStandbyTaskExecutor) execute(
|
|
ctx context.Context,
|
|
taskInfo tasks.Task,
|
|
shouldProcessTask bool,
|
|
) error {
|
|
switch task := taskInfo.(type) {
|
|
case *tasks.ActivityTask:
|
|
if !shouldProcessTask {
|
|
return nil
|
|
}
|
|
return t.processActivityTask(ctx, task)
|
|
case *tasks.WorkflowTask:
|
|
if !shouldProcessTask {
|
|
return nil
|
|
}
|
|
return t.processWorkflowTask(ctx, task)
|
|
case *tasks.CancelExecutionTask:
|
|
if !shouldProcessTask {
|
|
return nil
|
|
}
|
|
return t.processCancelExecution(ctx, task)
|
|
case *tasks.SignalExecutionTask:
|
|
if !shouldProcessTask {
|
|
return nil
|
|
}
|
|
return t.processSignalExecution(ctx, task)
|
|
case *tasks.StartChildExecutionTask:
|
|
if !shouldProcessTask {
|
|
return nil
|
|
}
|
|
return t.processStartChildExecution(ctx, task)
|
|
case *tasks.ResetWorkflowTask:
|
|
// no reset needed for standby
|
|
// TODO: add error logs
|
|
return nil
|
|
case *tasks.CloseExecutionTask:
|
|
return t.processCloseExecution(ctx, task)
|
|
case *tasks.DeleteExecutionTask:
|
|
return t.processDeleteExecutionTask(ctx, task)
|
|
default:
|
|
return errUnknownTransferTask
|
|
}
|
|
}
|
|
|
|
func (t *transferQueueStandbyTaskExecutor) processActivityTask(
|
|
ctx context.Context,
|
|
transferTask *tasks.ActivityTask,
|
|
) error {
|
|
|
|
processTaskIfClosed := false
|
|
actionFn := func(context workflow.Context, mutableState workflow.MutableState) (interface{}, error) {
|
|
|
|
activityInfo, ok := mutableState.GetActivityInfo(transferTask.ScheduleID)
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
|
|
ok = VerifyTaskVersion(t.shard, t.logger, mutableState.GetNamespaceEntry(), activityInfo.Version, transferTask.Version, transferTask)
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
|
|
if activityInfo.StartedId == common.EmptyEventID {
|
|
return newPushActivityToMatchingInfo(*activityInfo.ScheduleToStartTimeout), nil
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
return t.processTransfer(
|
|
ctx,
|
|
processTaskIfClosed,
|
|
transferTask,
|
|
actionFn,
|
|
getStandbyPostActionFn(
|
|
transferTask,
|
|
t.getCurrentTime,
|
|
t.config.StandbyTaskMissingEventsResendDelay(),
|
|
t.config.StandbyTaskMissingEventsDiscardDelay(),
|
|
t.pushActivity,
|
|
t.pushActivity,
|
|
),
|
|
)
|
|
}
|
|
|
|
func (t *transferQueueStandbyTaskExecutor) processWorkflowTask(
|
|
ctx context.Context,
|
|
transferTask *tasks.WorkflowTask,
|
|
) error {
|
|
|
|
processTaskIfClosed := false
|
|
actionFn := func(context workflow.Context, mutableState workflow.MutableState) (interface{}, error) {
|
|
|
|
wtInfo, ok := mutableState.GetWorkflowTaskInfo(transferTask.ScheduleID)
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
|
|
executionInfo := mutableState.GetExecutionInfo()
|
|
|
|
var taskQueue *taskqueuepb.TaskQueue
|
|
var taskScheduleToStartTimeoutSeconds = int64(0)
|
|
if mutableState.GetExecutionInfo().TaskQueue != transferTask.TaskQueue {
|
|
// this workflowTask is an sticky workflowTask
|
|
// there shall already be an timer set
|
|
taskQueue = &taskqueuepb.TaskQueue{
|
|
Name: transferTask.TaskQueue,
|
|
Kind: enumspb.TASK_QUEUE_KIND_STICKY,
|
|
}
|
|
taskScheduleToStartTimeoutSeconds = int64(timestamp.DurationValue(executionInfo.StickyScheduleToStartTimeout).Seconds())
|
|
} else {
|
|
taskQueue = &taskqueuepb.TaskQueue{
|
|
Name: transferTask.TaskQueue,
|
|
Kind: enumspb.TASK_QUEUE_KIND_NORMAL,
|
|
}
|
|
workflowRunTimeout := timestamp.DurationValue(executionInfo.WorkflowRunTimeout)
|
|
taskScheduleToStartTimeoutSeconds = int64(workflowRunTimeout.Round(time.Second).Seconds())
|
|
}
|
|
|
|
ok = VerifyTaskVersion(t.shard, t.logger, mutableState.GetNamespaceEntry(), wtInfo.Version, transferTask.Version, transferTask)
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
|
|
if wtInfo.StartedID == common.EmptyEventID {
|
|
return newPushWorkflowTaskToMatchingInfo(
|
|
taskScheduleToStartTimeoutSeconds,
|
|
*taskQueue,
|
|
), nil
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
return t.processTransfer(
|
|
ctx,
|
|
processTaskIfClosed,
|
|
transferTask,
|
|
actionFn,
|
|
getStandbyPostActionFn(
|
|
transferTask,
|
|
t.getCurrentTime,
|
|
t.config.StandbyTaskMissingEventsResendDelay(),
|
|
t.config.StandbyTaskMissingEventsDiscardDelay(),
|
|
t.pushWorkflowTask,
|
|
t.pushWorkflowTask,
|
|
),
|
|
)
|
|
}
|
|
|
|
func (t *transferQueueStandbyTaskExecutor) processCloseExecution(
|
|
ctx context.Context,
|
|
transferTask *tasks.CloseExecutionTask,
|
|
) error {
|
|
|
|
processTaskIfClosed := true
|
|
actionFn := func(context workflow.Context, mutableState workflow.MutableState) (interface{}, error) {
|
|
|
|
if mutableState.IsWorkflowExecutionRunning() {
|
|
// this can happen if workflow is reset.
|
|
return nil, nil
|
|
}
|
|
|
|
completionEvent, err := mutableState.GetCompletionEvent(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
wfCloseTime := timestamp.TimeValue(completionEvent.GetEventTime())
|
|
|
|
executionInfo := mutableState.GetExecutionInfo()
|
|
executionState := mutableState.GetExecutionState()
|
|
workflowTypeName := executionInfo.WorkflowTypeName
|
|
workflowCloseTime := wfCloseTime
|
|
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
|
|
}
|
|
ok := VerifyTaskVersion(t.shard, t.logger, mutableState.GetNamespaceEntry(), lastWriteVersion, transferTask.Version, transferTask)
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
|
|
// DO NOT REPLY TO PARENT
|
|
// since event replication should be done by active cluster
|
|
return nil, t.recordWorkflowClosed(
|
|
namespace.ID(transferTask.NamespaceID),
|
|
transferTask.WorkflowID,
|
|
transferTask.RunID,
|
|
workflowTypeName,
|
|
workflowStartTime,
|
|
workflowExecutionTime,
|
|
workflowCloseTime,
|
|
workflowStatus,
|
|
workflowHistoryLength,
|
|
visibilityMemo,
|
|
searchAttr,
|
|
)
|
|
}
|
|
|
|
return t.processTransfer(
|
|
ctx,
|
|
processTaskIfClosed,
|
|
transferTask,
|
|
actionFn,
|
|
standbyTaskPostActionNoOp,
|
|
) // no op post action, since the entire workflow is finished
|
|
}
|
|
|
|
func (t *transferQueueStandbyTaskExecutor) processCancelExecution(
|
|
ctx context.Context,
|
|
transferTask *tasks.CancelExecutionTask,
|
|
) error {
|
|
|
|
processTaskIfClosed := false
|
|
actionFn := func(context workflow.Context, mutableState workflow.MutableState) (interface{}, error) {
|
|
|
|
requestCancelInfo, ok := mutableState.GetRequestCancelInfo(transferTask.InitiatedID)
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
|
|
ok = VerifyTaskVersion(t.shard, t.logger, mutableState.GetNamespaceEntry(), requestCancelInfo.Version, transferTask.Version, transferTask)
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
|
|
return getHistoryResendInfo(mutableState)
|
|
}
|
|
|
|
return t.processTransfer(
|
|
ctx,
|
|
processTaskIfClosed,
|
|
transferTask,
|
|
actionFn,
|
|
getStandbyPostActionFn(
|
|
transferTask,
|
|
t.getCurrentTime,
|
|
t.config.StandbyTaskMissingEventsResendDelay(),
|
|
t.config.StandbyTaskMissingEventsDiscardDelay(),
|
|
t.fetchHistoryFromRemote,
|
|
standbyTransferTaskPostActionTaskDiscarded,
|
|
),
|
|
)
|
|
}
|
|
|
|
func (t *transferQueueStandbyTaskExecutor) processSignalExecution(
|
|
ctx context.Context,
|
|
transferTask *tasks.SignalExecutionTask,
|
|
) error {
|
|
|
|
processTaskIfClosed := false
|
|
actionFn := func(context workflow.Context, mutableState workflow.MutableState) (interface{}, error) {
|
|
|
|
signalInfo, ok := mutableState.GetSignalInfo(transferTask.InitiatedID)
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
|
|
ok = VerifyTaskVersion(t.shard, t.logger, mutableState.GetNamespaceEntry(), signalInfo.Version, transferTask.Version, transferTask)
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
|
|
return getHistoryResendInfo(mutableState)
|
|
}
|
|
|
|
return t.processTransfer(
|
|
ctx,
|
|
processTaskIfClosed,
|
|
transferTask,
|
|
actionFn,
|
|
getStandbyPostActionFn(
|
|
transferTask,
|
|
t.getCurrentTime,
|
|
t.config.StandbyTaskMissingEventsResendDelay(),
|
|
t.config.StandbyTaskMissingEventsDiscardDelay(),
|
|
t.fetchHistoryFromRemote,
|
|
standbyTransferTaskPostActionTaskDiscarded,
|
|
),
|
|
)
|
|
}
|
|
|
|
func (t *transferQueueStandbyTaskExecutor) processStartChildExecution(
|
|
ctx context.Context,
|
|
transferTask *tasks.StartChildExecutionTask,
|
|
) error {
|
|
|
|
processTaskIfClosed := false
|
|
actionFn := func(context workflow.Context, mutableState workflow.MutableState) (interface{}, error) {
|
|
|
|
childWorkflowInfo, ok := mutableState.GetChildExecutionInfo(transferTask.InitiatedID)
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
|
|
ok = VerifyTaskVersion(t.shard, t.logger, mutableState.GetNamespaceEntry(), childWorkflowInfo.Version, transferTask.Version, transferTask)
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
|
|
if childWorkflowInfo.StartedId != common.EmptyEventID {
|
|
return nil, nil
|
|
}
|
|
// TODO: standby logic should verify if first workflow task is scheduled or not as well?
|
|
|
|
return getHistoryResendInfo(mutableState)
|
|
}
|
|
|
|
return t.processTransfer(
|
|
ctx,
|
|
processTaskIfClosed,
|
|
transferTask,
|
|
actionFn,
|
|
getStandbyPostActionFn(
|
|
transferTask,
|
|
t.getCurrentTime,
|
|
t.config.StandbyTaskMissingEventsResendDelay(),
|
|
t.config.StandbyTaskMissingEventsDiscardDelay(),
|
|
t.fetchHistoryFromRemote,
|
|
standbyTransferTaskPostActionTaskDiscarded,
|
|
),
|
|
)
|
|
}
|
|
|
|
func (t *transferQueueStandbyTaskExecutor) processTransfer(
|
|
ctx context.Context,
|
|
processTaskIfClosed bool,
|
|
taskInfo tasks.Task,
|
|
actionFn standbyActionFn,
|
|
postActionFn standbyPostActionFn,
|
|
) (retError error) {
|
|
var cancel context.CancelFunc
|
|
ctx, cancel = context.WithTimeout(ctx, taskTimeout)
|
|
defer cancel()
|
|
namespaceID, execution := t.getNamespaceIDAndWorkflowExecution(taskInfo)
|
|
context, release, err := t.cache.GetOrCreateWorkflowExecution(
|
|
ctx,
|
|
namespaceID,
|
|
execution,
|
|
workflow.CallerTypeTask,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() {
|
|
if retError == consts.ErrTaskRetry {
|
|
release(nil)
|
|
} else {
|
|
release(retError)
|
|
}
|
|
}()
|
|
|
|
mutableState, err := loadMutableStateForTransferTask(ctx, context, taskInfo, t.metricsClient, t.logger)
|
|
if err != nil || mutableState == nil {
|
|
return err
|
|
}
|
|
|
|
if !mutableState.IsWorkflowExecutionRunning() && !processTaskIfClosed {
|
|
// workflow already finished, no need to process the timer
|
|
return nil
|
|
}
|
|
|
|
historyResendInfo, err := actionFn(context, mutableState)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// NOTE: do not access anything related mutable state after this lock release
|
|
release(nil)
|
|
return postActionFn(taskInfo, historyResendInfo, t.logger)
|
|
}
|
|
|
|
func (t *transferQueueStandbyTaskExecutor) pushActivity(
|
|
task tasks.Task,
|
|
postActionInfo interface{},
|
|
logger log.Logger,
|
|
) error {
|
|
|
|
if postActionInfo == nil {
|
|
return nil
|
|
}
|
|
|
|
pushActivityInfo := postActionInfo.(*pushActivityTaskToMatchingInfo)
|
|
timeout := pushActivityInfo.activityTaskScheduleToStartTimeout
|
|
return t.transferQueueTaskExecutorBase.pushActivity(
|
|
task.(*tasks.ActivityTask),
|
|
&timeout,
|
|
)
|
|
}
|
|
|
|
func (t *transferQueueStandbyTaskExecutor) pushWorkflowTask(
|
|
task tasks.Task,
|
|
postActionInfo interface{},
|
|
logger log.Logger,
|
|
) error {
|
|
|
|
if postActionInfo == nil {
|
|
return nil
|
|
}
|
|
|
|
pushwtInfo := postActionInfo.(*pushWorkflowTaskToMatchingInfo)
|
|
timeout := pushwtInfo.workflowTaskScheduleToStartTimeout
|
|
return t.transferQueueTaskExecutorBase.pushWorkflowTask(
|
|
task.(*tasks.WorkflowTask),
|
|
&pushwtInfo.taskqueue,
|
|
timestamp.DurationFromSeconds(timeout),
|
|
)
|
|
}
|
|
|
|
func (t *transferQueueStandbyTaskExecutor) fetchHistoryFromRemote(
|
|
taskInfo tasks.Task,
|
|
postActionInfo interface{},
|
|
log log.Logger,
|
|
) error {
|
|
|
|
if postActionInfo == nil {
|
|
return nil
|
|
}
|
|
|
|
resendInfo := postActionInfo.(*historyResendInfo)
|
|
|
|
t.metricsClient.IncCounter(metrics.HistoryRereplicationByTransferTaskScope, metrics.ClientRequests)
|
|
stopwatch := t.metricsClient.StartTimer(metrics.HistoryRereplicationByTransferTaskScope, metrics.ClientLatency)
|
|
defer stopwatch.Stop()
|
|
|
|
var err error
|
|
if resendInfo.lastEventID != common.EmptyEventID && resendInfo.lastEventVersion != common.EmptyVersion {
|
|
if err := refreshTasks(
|
|
t.adminClient,
|
|
t.shard.GetNamespaceRegistry(),
|
|
namespace.ID(taskInfo.GetNamespaceID()),
|
|
taskInfo.GetWorkflowID(),
|
|
taskInfo.GetRunID(),
|
|
); err != nil {
|
|
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(t.clusterName))
|
|
}
|
|
err = t.nDCHistoryResender.SendSingleWorkflowHistory(
|
|
namespace.ID(taskInfo.GetNamespaceID()),
|
|
taskInfo.GetWorkflowID(),
|
|
taskInfo.GetRunID(),
|
|
resendInfo.lastEventID,
|
|
resendInfo.lastEventVersion,
|
|
0,
|
|
0,
|
|
)
|
|
} else {
|
|
err = serviceerror.NewInternal(
|
|
"transferQueueStandbyProcessor encountered empty historyResendInfo",
|
|
)
|
|
}
|
|
|
|
if err != nil {
|
|
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(t.clusterName))
|
|
}
|
|
|
|
// return error so task processing logic will retry
|
|
return consts.ErrTaskRetry
|
|
}
|
|
|
|
func (t *transferQueueStandbyTaskExecutor) getCurrentTime() time.Time {
|
|
return t.shard.GetCurrentTime(t.clusterName)
|
|
}
|