mirror of
https://github.com/rocky-linux/peridot.git
synced 2024-11-01 04:41:22 +00:00
1476 lines
51 KiB
Go
1476 lines
51 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 internal
|
|
|
|
// All code in this file is private to the package.
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/gogo/protobuf/proto"
|
|
commandpb "go.temporal.io/api/command/v1"
|
|
commonpb "go.temporal.io/api/common/v1"
|
|
enumspb "go.temporal.io/api/enums/v1"
|
|
failurepb "go.temporal.io/api/failure/v1"
|
|
historypb "go.temporal.io/api/history/v1"
|
|
taskqueuepb "go.temporal.io/api/taskqueue/v1"
|
|
"go.temporal.io/sdk/converter"
|
|
"go.temporal.io/sdk/internal/common"
|
|
"go.temporal.io/sdk/internal/common/metrics"
|
|
ilog "go.temporal.io/sdk/internal/log"
|
|
"go.temporal.io/sdk/log"
|
|
)
|
|
|
|
const (
|
|
queryResultSizeLimit = 2000000 // 2MB
|
|
)
|
|
|
|
// Assert that structs do indeed implement the interfaces
|
|
var _ WorkflowEnvironment = (*workflowEnvironmentImpl)(nil)
|
|
var _ workflowExecutionEventHandler = (*workflowExecutionEventHandlerImpl)(nil)
|
|
|
|
type (
|
|
// completionHandler Handler to indicate completion result
|
|
completionHandler func(result *commonpb.Payloads, err error)
|
|
|
|
// workflowExecutionEventHandlerImpl handler to handle workflowExecutionEventHandler
|
|
workflowExecutionEventHandlerImpl struct {
|
|
*workflowEnvironmentImpl
|
|
workflowDefinition WorkflowDefinition
|
|
}
|
|
|
|
scheduledTimer struct {
|
|
callback ResultHandler
|
|
handled bool
|
|
}
|
|
|
|
scheduledActivity struct {
|
|
callback ResultHandler
|
|
waitForCancelRequest bool
|
|
handled bool
|
|
activityType ActivityType
|
|
}
|
|
|
|
scheduledChildWorkflow struct {
|
|
resultCallback ResultHandler
|
|
startedCallback func(r WorkflowExecution, e error)
|
|
waitForCancellation bool
|
|
handled bool
|
|
}
|
|
|
|
scheduledCancellation struct {
|
|
callback ResultHandler
|
|
handled bool
|
|
}
|
|
|
|
scheduledSignal struct {
|
|
callback ResultHandler
|
|
handled bool
|
|
}
|
|
|
|
// workflowEnvironmentImpl an implementation of WorkflowEnvironment represents a environment for workflow execution.
|
|
workflowEnvironmentImpl struct {
|
|
workflowInfo *WorkflowInfo
|
|
|
|
commandsHelper *commandsHelper
|
|
sideEffectResult map[int64]*commonpb.Payloads
|
|
changeVersions map[string]Version
|
|
pendingLaTasks map[string]*localActivityTask
|
|
mutableSideEffect map[string]*commonpb.Payloads
|
|
unstartedLaTasks map[string]struct{}
|
|
openSessions map[string]*SessionInfo
|
|
|
|
// LocalActivities have a separate, individual counter instead of relying on actual commandEventIDs.
|
|
// This is because command IDs are only incremented on activity completion, which breaks
|
|
// local activities that are spawned in parallel as they would all share the same command ID
|
|
localActivityCounterID int64
|
|
|
|
sideEffectCounterID int64
|
|
|
|
currentReplayTime time.Time // Indicates current replay time of the command.
|
|
currentLocalTime time.Time // Local time when currentReplayTime was updated.
|
|
|
|
completeHandler completionHandler // events completion handler
|
|
cancelHandler func() // A cancel handler to be invoked on a cancel notification
|
|
signalHandler func(name string, input *commonpb.Payloads, header *commonpb.Header) error // A signal handler to be invoked on a signal event
|
|
queryHandler func(queryType string, queryArgs *commonpb.Payloads, header *commonpb.Header) (*commonpb.Payloads, error)
|
|
|
|
logger log.Logger
|
|
isReplay bool // flag to indicate if workflow is in replay mode
|
|
enableLoggingInReplay bool // flag to indicate if workflow should enable logging in replay mode
|
|
|
|
metricsHandler metrics.Handler
|
|
registry *registry
|
|
dataConverter converter.DataConverter
|
|
contextPropagators []ContextPropagator
|
|
deadlockDetectionTimeout time.Duration
|
|
}
|
|
|
|
localActivityTask struct {
|
|
sync.Mutex
|
|
workflowTask *workflowTask
|
|
activityID string
|
|
params *ExecuteLocalActivityParams
|
|
callback LocalActivityResultHandler
|
|
wc *workflowExecutionContextImpl
|
|
canceled bool
|
|
cancelFunc func()
|
|
attempt int32 // attempt starting from 1
|
|
retryPolicy *RetryPolicy
|
|
expireTime time.Time
|
|
header *commonpb.Header
|
|
}
|
|
|
|
localActivityMarkerData struct {
|
|
ActivityID string
|
|
ActivityType string
|
|
ReplayTime time.Time
|
|
Attempt int32 // record attempt, starting from 1.
|
|
Backoff time.Duration // retry backoff duration.
|
|
}
|
|
)
|
|
|
|
var (
|
|
// ErrUnknownMarkerName is returned if there is unknown marker name in the history.
|
|
ErrUnknownMarkerName = errors.New("unknown marker name")
|
|
// ErrMissingMarkerDetails is returned when marker details are nil.
|
|
ErrMissingMarkerDetails = errors.New("marker details are nil")
|
|
// ErrMissingMarkerDataKey is returned when marker details doesn't have data key.
|
|
ErrMissingMarkerDataKey = errors.New("marker key is missing in details")
|
|
)
|
|
|
|
func newWorkflowExecutionEventHandler(
|
|
workflowInfo *WorkflowInfo,
|
|
completeHandler completionHandler,
|
|
logger log.Logger,
|
|
enableLoggingInReplay bool,
|
|
metricsHandler metrics.Handler,
|
|
registry *registry,
|
|
dataConverter converter.DataConverter,
|
|
contextPropagators []ContextPropagator,
|
|
deadlockDetectionTimeout time.Duration,
|
|
) workflowExecutionEventHandler {
|
|
context := &workflowEnvironmentImpl{
|
|
workflowInfo: workflowInfo,
|
|
commandsHelper: newCommandsHelper(),
|
|
sideEffectResult: make(map[int64]*commonpb.Payloads),
|
|
mutableSideEffect: make(map[string]*commonpb.Payloads),
|
|
changeVersions: make(map[string]Version),
|
|
pendingLaTasks: make(map[string]*localActivityTask),
|
|
unstartedLaTasks: make(map[string]struct{}),
|
|
openSessions: make(map[string]*SessionInfo),
|
|
completeHandler: completeHandler,
|
|
enableLoggingInReplay: enableLoggingInReplay,
|
|
registry: registry,
|
|
dataConverter: dataConverter,
|
|
contextPropagators: contextPropagators,
|
|
deadlockDetectionTimeout: deadlockDetectionTimeout,
|
|
}
|
|
context.logger = ilog.NewReplayLogger(
|
|
log.With(logger,
|
|
tagWorkflowType, workflowInfo.WorkflowType.Name,
|
|
tagWorkflowID, workflowInfo.WorkflowExecution.ID,
|
|
tagRunID, workflowInfo.WorkflowExecution.RunID,
|
|
tagAttempt, workflowInfo.Attempt,
|
|
),
|
|
&context.isReplay,
|
|
&context.enableLoggingInReplay)
|
|
|
|
if metricsHandler != nil {
|
|
context.metricsHandler = metrics.NewReplayAwareHandler(&context.isReplay, metricsHandler).
|
|
WithTags(metrics.WorkflowTags(workflowInfo.WorkflowType.Name))
|
|
}
|
|
|
|
return &workflowExecutionEventHandlerImpl{context, nil}
|
|
}
|
|
|
|
func (s *scheduledTimer) handle(result *commonpb.Payloads, err error) {
|
|
if s.handled {
|
|
panic(fmt.Sprintf("timer already handled %v", s))
|
|
}
|
|
s.handled = true
|
|
s.callback(result, err)
|
|
}
|
|
|
|
func (s *scheduledActivity) handle(result *commonpb.Payloads, err error) {
|
|
if s.handled {
|
|
panic(fmt.Sprintf("activity already handled %v", s))
|
|
}
|
|
s.handled = true
|
|
s.callback(result, err)
|
|
}
|
|
|
|
func (s *scheduledChildWorkflow) handle(result *commonpb.Payloads, err error) {
|
|
if s.handled {
|
|
panic(fmt.Sprintf("child workflow already handled %v", s))
|
|
}
|
|
s.handled = true
|
|
s.resultCallback(result, err)
|
|
}
|
|
|
|
func (s *scheduledChildWorkflow) handleFailedToStart(result *commonpb.Payloads, err error) {
|
|
if s.handled {
|
|
panic(fmt.Sprintf("child workflow already handled %v", s))
|
|
}
|
|
s.handled = true
|
|
s.resultCallback(result, err)
|
|
s.startedCallback(WorkflowExecution{}, err)
|
|
}
|
|
|
|
func (t *localActivityTask) cancel() {
|
|
t.Lock()
|
|
t.canceled = true
|
|
if t.cancelFunc != nil {
|
|
t.cancelFunc()
|
|
}
|
|
t.Unlock()
|
|
}
|
|
|
|
func (s *scheduledCancellation) handle(result *commonpb.Payloads, err error) {
|
|
if s.handled {
|
|
panic(fmt.Sprintf("cancellation already handled %v", s))
|
|
}
|
|
s.handled = true
|
|
s.callback(result, err)
|
|
}
|
|
|
|
func (s *scheduledSignal) handle(result *commonpb.Payloads, err error) {
|
|
if s.handled {
|
|
panic(fmt.Sprintf("signal already handled %v", s))
|
|
}
|
|
s.handled = true
|
|
s.callback(result, err)
|
|
}
|
|
|
|
func (wc *workflowEnvironmentImpl) getNextLocalActivityID() string {
|
|
wc.localActivityCounterID++
|
|
return getStringID(wc.localActivityCounterID)
|
|
}
|
|
|
|
func (wc *workflowEnvironmentImpl) getNextSideEffectID() int64 {
|
|
wc.sideEffectCounterID++
|
|
return wc.sideEffectCounterID
|
|
}
|
|
|
|
func (wc *workflowEnvironmentImpl) WorkflowInfo() *WorkflowInfo {
|
|
return wc.workflowInfo
|
|
}
|
|
|
|
func (wc *workflowEnvironmentImpl) Complete(result *commonpb.Payloads, err error) {
|
|
wc.completeHandler(result, err)
|
|
}
|
|
|
|
func (wc *workflowEnvironmentImpl) RequestCancelChildWorkflow(namespace string, workflowID string) {
|
|
// For cancellation of child workflow only, we do not use cancellation ID and run ID
|
|
wc.commandsHelper.requestCancelExternalWorkflowExecution(namespace, workflowID, "", "", true)
|
|
}
|
|
|
|
func (wc *workflowEnvironmentImpl) RequestCancelExternalWorkflow(namespace, workflowID, runID string, callback ResultHandler) {
|
|
// for cancellation of external workflow, we have to use cancellation ID and set isChildWorkflowOnly to false
|
|
cancellationID := wc.GenerateSequenceID()
|
|
command := wc.commandsHelper.requestCancelExternalWorkflowExecution(namespace, workflowID, runID, cancellationID, false)
|
|
command.setData(&scheduledCancellation{callback: callback})
|
|
}
|
|
|
|
func (wc *workflowEnvironmentImpl) SignalExternalWorkflow(
|
|
namespace string,
|
|
workflowID string,
|
|
runID string,
|
|
signalName string,
|
|
input *commonpb.Payloads,
|
|
_ /* THIS IS FOR TEST FRAMEWORK. DO NOT USE HERE. */ interface{},
|
|
header *commonpb.Header,
|
|
childWorkflowOnly bool,
|
|
callback ResultHandler,
|
|
) {
|
|
|
|
signalID := wc.GenerateSequenceID()
|
|
command := wc.commandsHelper.signalExternalWorkflowExecution(namespace, workflowID, runID, signalName, input,
|
|
header, signalID, childWorkflowOnly)
|
|
command.setData(&scheduledSignal{callback: callback})
|
|
}
|
|
|
|
func (wc *workflowEnvironmentImpl) UpsertSearchAttributes(attributes map[string]interface{}) error {
|
|
// This has to be used in WorkflowEnvironment implementations instead of in Workflow for testsuite mock purpose.
|
|
attr, err := validateAndSerializeSearchAttributes(attributes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var upsertID string
|
|
if changeVersion, ok := attributes[TemporalChangeVersion]; ok {
|
|
// to ensure backward compatibility on searchable GetVersion, use latest changeVersion as upsertID
|
|
upsertID = changeVersion.([]string)[0]
|
|
} else {
|
|
upsertID = wc.GenerateSequenceID()
|
|
}
|
|
|
|
wc.commandsHelper.upsertSearchAttributes(upsertID, attr)
|
|
wc.updateWorkflowInfoWithSearchAttributes(attr) // this is for getInfo correctness
|
|
return nil
|
|
}
|
|
|
|
func (wc *workflowEnvironmentImpl) updateWorkflowInfoWithSearchAttributes(attributes *commonpb.SearchAttributes) {
|
|
wc.workflowInfo.SearchAttributes = mergeSearchAttributes(wc.workflowInfo.SearchAttributes, attributes)
|
|
}
|
|
|
|
func mergeSearchAttributes(current, upsert *commonpb.SearchAttributes) *commonpb.SearchAttributes {
|
|
if current == nil || len(current.IndexedFields) == 0 {
|
|
if upsert == nil || len(upsert.IndexedFields) == 0 {
|
|
return nil
|
|
}
|
|
current = &commonpb.SearchAttributes{
|
|
IndexedFields: make(map[string]*commonpb.Payload),
|
|
}
|
|
}
|
|
|
|
fields := current.IndexedFields
|
|
for k, v := range upsert.IndexedFields {
|
|
fields[k] = v
|
|
}
|
|
return current
|
|
}
|
|
|
|
func validateAndSerializeSearchAttributes(attributes map[string]interface{}) (*commonpb.SearchAttributes, error) {
|
|
if len(attributes) == 0 {
|
|
return nil, errSearchAttributesNotSet
|
|
}
|
|
attr, err := serializeSearchAttributes(attributes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return attr, nil
|
|
}
|
|
|
|
func (wc *workflowEnvironmentImpl) RegisterCancelHandler(handler func()) {
|
|
wrappedHandler := func() {
|
|
wc.commandsHelper.workflowExecutionIsCancelling = true
|
|
handler()
|
|
}
|
|
wc.cancelHandler = wrappedHandler
|
|
}
|
|
|
|
func (wc *workflowEnvironmentImpl) ExecuteChildWorkflow(
|
|
params ExecuteWorkflowParams, callback ResultHandler, startedHandler func(r WorkflowExecution, e error)) {
|
|
if params.WorkflowID == "" {
|
|
params.WorkflowID = wc.workflowInfo.WorkflowExecution.RunID + "_" + wc.GenerateSequenceID()
|
|
}
|
|
memo, err := getWorkflowMemo(params.Memo, wc.dataConverter)
|
|
if err != nil {
|
|
callback(nil, err)
|
|
return
|
|
}
|
|
searchAttr, err := serializeSearchAttributes(params.SearchAttributes)
|
|
if err != nil {
|
|
callback(nil, err)
|
|
return
|
|
}
|
|
|
|
attributes := &commandpb.StartChildWorkflowExecutionCommandAttributes{}
|
|
|
|
attributes.Namespace = params.Namespace
|
|
attributes.TaskQueue = &taskqueuepb.TaskQueue{Name: params.TaskQueueName, Kind: enumspb.TASK_QUEUE_KIND_NORMAL}
|
|
attributes.WorkflowId = params.WorkflowID
|
|
attributes.WorkflowExecutionTimeout = ¶ms.WorkflowExecutionTimeout
|
|
attributes.WorkflowRunTimeout = ¶ms.WorkflowRunTimeout
|
|
attributes.WorkflowTaskTimeout = ¶ms.WorkflowTaskTimeout
|
|
attributes.Input = params.Input
|
|
attributes.WorkflowType = &commonpb.WorkflowType{Name: params.WorkflowType.Name}
|
|
attributes.WorkflowIdReusePolicy = params.WorkflowIDReusePolicy
|
|
attributes.ParentClosePolicy = params.ParentClosePolicy
|
|
attributes.RetryPolicy = params.RetryPolicy
|
|
attributes.Header = params.Header
|
|
attributes.Memo = memo
|
|
attributes.SearchAttributes = searchAttr
|
|
if len(params.CronSchedule) > 0 {
|
|
attributes.CronSchedule = params.CronSchedule
|
|
}
|
|
|
|
command := wc.commandsHelper.startChildWorkflowExecution(attributes)
|
|
command.setData(&scheduledChildWorkflow{
|
|
resultCallback: callback,
|
|
startedCallback: startedHandler,
|
|
waitForCancellation: params.WaitForCancellation,
|
|
})
|
|
|
|
wc.logger.Debug("ExecuteChildWorkflow",
|
|
tagChildWorkflowID, params.WorkflowID,
|
|
tagWorkflowType, params.WorkflowType.Name)
|
|
}
|
|
|
|
func (wc *workflowEnvironmentImpl) RegisterSignalHandler(
|
|
handler func(name string, input *commonpb.Payloads, header *commonpb.Header) error,
|
|
) {
|
|
wc.signalHandler = handler
|
|
}
|
|
|
|
func (wc *workflowEnvironmentImpl) RegisterQueryHandler(
|
|
handler func(string, *commonpb.Payloads, *commonpb.Header) (*commonpb.Payloads, error),
|
|
) {
|
|
wc.queryHandler = handler
|
|
}
|
|
|
|
func (wc *workflowEnvironmentImpl) GetLogger() log.Logger {
|
|
return wc.logger
|
|
}
|
|
|
|
func (wc *workflowEnvironmentImpl) GetMetricsHandler() metrics.Handler {
|
|
return wc.metricsHandler
|
|
}
|
|
|
|
func (wc *workflowEnvironmentImpl) GetDataConverter() converter.DataConverter {
|
|
return wc.dataConverter
|
|
}
|
|
|
|
func (wc *workflowEnvironmentImpl) GetContextPropagators() []ContextPropagator {
|
|
return wc.contextPropagators
|
|
}
|
|
|
|
func (wc *workflowEnvironmentImpl) IsReplaying() bool {
|
|
return wc.isReplay
|
|
}
|
|
|
|
func (wc *workflowEnvironmentImpl) GenerateSequenceID() string {
|
|
return getStringID(wc.GenerateSequence())
|
|
}
|
|
|
|
func (wc *workflowEnvironmentImpl) GenerateSequence() int64 {
|
|
return wc.commandsHelper.getNextID()
|
|
}
|
|
|
|
func (wc *workflowEnvironmentImpl) CreateNewCommand(commandType enumspb.CommandType) *commandpb.Command {
|
|
return &commandpb.Command{
|
|
CommandType: commandType,
|
|
}
|
|
}
|
|
|
|
func (wc *workflowEnvironmentImpl) ExecuteActivity(parameters ExecuteActivityParams, callback ResultHandler) ActivityID {
|
|
scheduleTaskAttr := &commandpb.ScheduleActivityTaskCommandAttributes{}
|
|
scheduleID := wc.GenerateSequence()
|
|
if parameters.ActivityID == "" {
|
|
scheduleTaskAttr.ActivityId = getStringID(scheduleID)
|
|
} else {
|
|
scheduleTaskAttr.ActivityId = parameters.ActivityID
|
|
}
|
|
activityID := scheduleTaskAttr.GetActivityId()
|
|
scheduleTaskAttr.ActivityType = &commonpb.ActivityType{Name: parameters.ActivityType.Name}
|
|
scheduleTaskAttr.TaskQueue = &taskqueuepb.TaskQueue{Name: parameters.TaskQueueName, Kind: enumspb.TASK_QUEUE_KIND_NORMAL}
|
|
scheduleTaskAttr.Input = parameters.Input
|
|
scheduleTaskAttr.ScheduleToCloseTimeout = ¶meters.ScheduleToCloseTimeout
|
|
scheduleTaskAttr.StartToCloseTimeout = ¶meters.StartToCloseTimeout
|
|
scheduleTaskAttr.ScheduleToStartTimeout = ¶meters.ScheduleToStartTimeout
|
|
scheduleTaskAttr.HeartbeatTimeout = ¶meters.HeartbeatTimeout
|
|
scheduleTaskAttr.RetryPolicy = parameters.RetryPolicy
|
|
scheduleTaskAttr.Header = parameters.Header
|
|
|
|
command := wc.commandsHelper.scheduleActivityTask(scheduleID, scheduleTaskAttr)
|
|
command.setData(&scheduledActivity{
|
|
callback: callback,
|
|
waitForCancelRequest: parameters.WaitForCancellation,
|
|
activityType: parameters.ActivityType,
|
|
})
|
|
|
|
wc.logger.Debug("ExecuteActivity",
|
|
tagActivityID, activityID,
|
|
tagActivityType, scheduleTaskAttr.ActivityType.GetName())
|
|
|
|
return ActivityID{id: activityID}
|
|
}
|
|
|
|
func (wc *workflowEnvironmentImpl) RequestCancelActivity(activityID ActivityID) {
|
|
command := wc.commandsHelper.requestCancelActivityTask(activityID.id)
|
|
activity := command.getData().(*scheduledActivity)
|
|
if activity.handled {
|
|
return
|
|
}
|
|
|
|
if command.isDone() || !activity.waitForCancelRequest {
|
|
activity.handle(nil, ErrCanceled)
|
|
}
|
|
|
|
wc.logger.Debug("RequestCancelActivity", tagActivityID, activityID)
|
|
}
|
|
|
|
func (wc *workflowEnvironmentImpl) ExecuteLocalActivity(params ExecuteLocalActivityParams, callback LocalActivityResultHandler) LocalActivityID {
|
|
activityID := wc.getNextLocalActivityID()
|
|
task := newLocalActivityTask(params, callback, activityID)
|
|
wc.pendingLaTasks[activityID] = task
|
|
wc.unstartedLaTasks[activityID] = struct{}{}
|
|
return LocalActivityID{id: activityID}
|
|
}
|
|
|
|
func newLocalActivityTask(params ExecuteLocalActivityParams, callback LocalActivityResultHandler, activityID string) *localActivityTask {
|
|
task := &localActivityTask{
|
|
activityID: activityID,
|
|
params: ¶ms,
|
|
callback: callback,
|
|
retryPolicy: params.RetryPolicy,
|
|
attempt: params.Attempt,
|
|
header: params.Header,
|
|
}
|
|
|
|
if params.ScheduleToCloseTimeout > 0 {
|
|
task.expireTime = params.ScheduledTime.Add(params.ScheduleToCloseTimeout)
|
|
}
|
|
return task
|
|
}
|
|
|
|
func (wc *workflowEnvironmentImpl) RequestCancelLocalActivity(activityID LocalActivityID) {
|
|
if task, ok := wc.pendingLaTasks[activityID.id]; ok {
|
|
task.cancel()
|
|
}
|
|
}
|
|
|
|
func (wc *workflowEnvironmentImpl) SetCurrentReplayTime(replayTime time.Time) {
|
|
if replayTime.Before(wc.currentReplayTime) {
|
|
return
|
|
}
|
|
wc.currentReplayTime = replayTime
|
|
wc.currentLocalTime = time.Now()
|
|
}
|
|
|
|
func (wc *workflowEnvironmentImpl) Now() time.Time {
|
|
return wc.currentReplayTime
|
|
}
|
|
|
|
func (wc *workflowEnvironmentImpl) NewTimer(d time.Duration, callback ResultHandler) *TimerID {
|
|
if d < 0 {
|
|
callback(nil, fmt.Errorf("negative duration provided %v", d))
|
|
return nil
|
|
}
|
|
if d == 0 {
|
|
callback(nil, nil)
|
|
return nil
|
|
}
|
|
|
|
timerID := wc.GenerateSequenceID()
|
|
startTimerAttr := &commandpb.StartTimerCommandAttributes{}
|
|
startTimerAttr.TimerId = timerID
|
|
startTimerAttr.StartToFireTimeout = &d
|
|
|
|
command := wc.commandsHelper.startTimer(startTimerAttr)
|
|
command.setData(&scheduledTimer{callback: callback})
|
|
|
|
wc.logger.Debug("NewTimer",
|
|
tagTimerID, startTimerAttr.GetTimerId(),
|
|
"Duration", d)
|
|
|
|
return &TimerID{id: timerID}
|
|
}
|
|
|
|
func (wc *workflowEnvironmentImpl) RequestCancelTimer(timerID TimerID) {
|
|
command := wc.commandsHelper.cancelTimer(timerID)
|
|
timer := command.getData().(*scheduledTimer)
|
|
if timer != nil {
|
|
if timer.handled {
|
|
return
|
|
}
|
|
timer.handle(nil, ErrCanceled)
|
|
}
|
|
wc.logger.Debug("RequestCancelTimer", tagTimerID, timerID)
|
|
}
|
|
|
|
func validateVersion(changeID string, version, minSupported, maxSupported Version) {
|
|
if version < minSupported {
|
|
panic(fmt.Sprintf("Workflow code removed support of version %v. "+
|
|
"for \"%v\" changeID. The oldest supported version is %v",
|
|
version, changeID, minSupported))
|
|
}
|
|
if version > maxSupported {
|
|
panic(fmt.Sprintf("Workflow code is too old to support version %v "+
|
|
"for \"%v\" changeID. The maximum supported version is %v",
|
|
version, changeID, maxSupported))
|
|
}
|
|
}
|
|
|
|
func (wc *workflowEnvironmentImpl) GetVersion(changeID string, minSupported, maxSupported Version) Version {
|
|
if version, ok := wc.changeVersions[changeID]; ok {
|
|
validateVersion(changeID, version, minSupported, maxSupported)
|
|
return version
|
|
}
|
|
|
|
var version Version
|
|
if wc.isReplay {
|
|
// GetVersion for changeID is called first time in replay mode, use DefaultVersion
|
|
version = DefaultVersion
|
|
} else {
|
|
// GetVersion for changeID is called first time (non-replay mode), generate a marker command for it.
|
|
// Also upsert search attributes to enable ability to search by changeVersion.
|
|
version = maxSupported
|
|
wc.commandsHelper.recordVersionMarker(changeID, version, wc.GetDataConverter())
|
|
_ = wc.UpsertSearchAttributes(createSearchAttributesForChangeVersion(changeID, version, wc.changeVersions))
|
|
}
|
|
|
|
validateVersion(changeID, version, minSupported, maxSupported)
|
|
wc.changeVersions[changeID] = version
|
|
return version
|
|
}
|
|
|
|
func createSearchAttributesForChangeVersion(changeID string, version Version, existingChangeVersions map[string]Version) map[string]interface{} {
|
|
return map[string]interface{}{
|
|
TemporalChangeVersion: getChangeVersions(changeID, version, existingChangeVersions),
|
|
}
|
|
}
|
|
|
|
func getChangeVersions(changeID string, version Version, existingChangeVersions map[string]Version) []string {
|
|
res := []string{getChangeVersion(changeID, version)}
|
|
for k, v := range existingChangeVersions {
|
|
res = append(res, getChangeVersion(k, v))
|
|
}
|
|
return res
|
|
}
|
|
|
|
func getChangeVersion(changeID string, version Version) string {
|
|
return fmt.Sprintf("%s-%v", changeID, version)
|
|
}
|
|
|
|
func (wc *workflowEnvironmentImpl) SideEffect(f func() (*commonpb.Payloads, error), callback ResultHandler) {
|
|
sideEffectID := wc.getNextSideEffectID()
|
|
var result *commonpb.Payloads
|
|
if wc.isReplay {
|
|
var ok bool
|
|
result, ok = wc.sideEffectResult[sideEffectID]
|
|
if !ok {
|
|
keys := make([]int64, 0, len(wc.sideEffectResult))
|
|
for k := range wc.sideEffectResult {
|
|
keys = append(keys, k)
|
|
}
|
|
panic(fmt.Sprintf("No cached result found for side effectID=%v. KnownSideEffects=%v",
|
|
sideEffectID, keys))
|
|
}
|
|
|
|
// Once the SideEffect has been consumed, we can free the referenced payload
|
|
// to reduce memory pressure
|
|
delete(wc.sideEffectResult, sideEffectID)
|
|
wc.logger.Debug("SideEffect returning already calculated result.",
|
|
tagSideEffectID, sideEffectID)
|
|
} else {
|
|
var err error
|
|
result, err = f()
|
|
if err != nil {
|
|
callback(result, err)
|
|
return
|
|
}
|
|
}
|
|
|
|
wc.commandsHelper.recordSideEffectMarker(sideEffectID, result, wc.dataConverter)
|
|
|
|
callback(result, nil)
|
|
wc.logger.Debug("SideEffect Marker added", tagSideEffectID, sideEffectID)
|
|
}
|
|
|
|
func (wc *workflowEnvironmentImpl) MutableSideEffect(id string, f func() interface{}, equals func(a, b interface{}) bool) converter.EncodedValue {
|
|
if result, ok := wc.mutableSideEffect[id]; ok {
|
|
encodedResult := newEncodedValue(result, wc.GetDataConverter())
|
|
if wc.isReplay {
|
|
return encodedResult
|
|
}
|
|
|
|
newValue := f()
|
|
if wc.isEqualValue(newValue, result, equals) {
|
|
return encodedResult
|
|
}
|
|
|
|
return wc.recordMutableSideEffect(id, wc.encodeValue(newValue))
|
|
}
|
|
|
|
if wc.isReplay {
|
|
// This should not happen
|
|
panic(fmt.Sprintf("Non deterministic workflow code change detected. MutableSideEffect API call doesn't have a correspondent event in the workflow history. MutableSideEffect ID: %s", id))
|
|
}
|
|
|
|
return wc.recordMutableSideEffect(id, wc.encodeValue(f()))
|
|
}
|
|
|
|
func (wc *workflowEnvironmentImpl) isEqualValue(newValue interface{}, encodedOldValue *commonpb.Payloads, equals func(a, b interface{}) bool) bool {
|
|
if newValue == nil {
|
|
// new value is nil
|
|
newEncodedValue := wc.encodeValue(nil)
|
|
return proto.Equal(newEncodedValue, encodedOldValue)
|
|
}
|
|
|
|
oldValue := decodeValue(newEncodedValue(encodedOldValue, wc.GetDataConverter()), newValue)
|
|
return equals(newValue, oldValue)
|
|
}
|
|
|
|
func decodeValue(encodedValue converter.EncodedValue, value interface{}) interface{} {
|
|
// We need to decode oldValue out of encodedValue, first we need to prepare valuePtr as the same type as value
|
|
valuePtr := reflect.New(reflect.TypeOf(value)).Interface()
|
|
if err := encodedValue.Get(valuePtr); err != nil {
|
|
panic(err)
|
|
}
|
|
decodedValue := reflect.ValueOf(valuePtr).Elem().Interface()
|
|
return decodedValue
|
|
}
|
|
|
|
func (wc *workflowEnvironmentImpl) encodeValue(value interface{}) *commonpb.Payloads {
|
|
payload, err := wc.encodeArg(value)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return payload
|
|
}
|
|
|
|
func (wc *workflowEnvironmentImpl) encodeArg(arg interface{}) (*commonpb.Payloads, error) {
|
|
return wc.GetDataConverter().ToPayloads(arg)
|
|
}
|
|
|
|
func (wc *workflowEnvironmentImpl) recordMutableSideEffect(id string, data *commonpb.Payloads) converter.EncodedValue {
|
|
details, err := encodeArgs(wc.GetDataConverter(), []interface{}{id, data})
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
wc.commandsHelper.recordMutableSideEffectMarker(id, details, wc.dataConverter)
|
|
wc.mutableSideEffect[id] = data
|
|
return newEncodedValue(data, wc.GetDataConverter())
|
|
}
|
|
|
|
func (wc *workflowEnvironmentImpl) AddSession(sessionInfo *SessionInfo) {
|
|
wc.openSessions[sessionInfo.SessionID] = sessionInfo
|
|
}
|
|
|
|
func (wc *workflowEnvironmentImpl) RemoveSession(sessionID string) {
|
|
delete(wc.openSessions, sessionID)
|
|
}
|
|
|
|
func (wc *workflowEnvironmentImpl) getOpenSessions() []*SessionInfo {
|
|
openSessions := make([]*SessionInfo, 0, len(wc.openSessions))
|
|
for _, info := range wc.openSessions {
|
|
openSessions = append(openSessions, info)
|
|
}
|
|
return openSessions
|
|
}
|
|
|
|
func (wc *workflowEnvironmentImpl) GetRegistry() *registry {
|
|
return wc.registry
|
|
}
|
|
|
|
func (weh *workflowExecutionEventHandlerImpl) ProcessEvent(
|
|
event *historypb.HistoryEvent,
|
|
isReplay bool,
|
|
isLast bool,
|
|
) (err error) {
|
|
if event == nil {
|
|
return errors.New("nil event provided")
|
|
}
|
|
defer func() {
|
|
if p := recover(); p != nil {
|
|
weh.metricsHandler.Counter(metrics.WorkflowTaskExecutionFailureCounter).Inc(1)
|
|
topLine := fmt.Sprintf("process event for %s [panic]:", weh.workflowInfo.TaskQueueName)
|
|
st := getStackTraceRaw(topLine, 7, 0)
|
|
weh.Complete(nil, newWorkflowPanicError(p, st))
|
|
}
|
|
}()
|
|
|
|
weh.isReplay = isReplay
|
|
traceLog(func() {
|
|
weh.logger.Debug("ProcessEvent",
|
|
tagEventID, event.GetEventId(),
|
|
tagEventType, event.GetEventType().String())
|
|
})
|
|
|
|
switch event.GetEventType() {
|
|
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_STARTED:
|
|
err = weh.handleWorkflowExecutionStarted(event.GetWorkflowExecutionStartedEventAttributes())
|
|
|
|
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_COMPLETED:
|
|
// No Operation
|
|
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_FAILED:
|
|
// No Operation
|
|
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_TIMED_OUT:
|
|
// No Operation
|
|
case enumspb.EVENT_TYPE_WORKFLOW_TASK_SCHEDULED:
|
|
// No Operation
|
|
case enumspb.EVENT_TYPE_WORKFLOW_TASK_STARTED:
|
|
// Set replay clock.
|
|
weh.SetCurrentReplayTime(common.TimeValue(event.GetEventTime()))
|
|
// Reset the counter on command helper used for generating ID for commands
|
|
weh.commandsHelper.setCurrentWorkflowTaskStartedEventID(event.GetEventId())
|
|
weh.workflowDefinition.OnWorkflowTaskStarted(weh.deadlockDetectionTimeout)
|
|
|
|
case enumspb.EVENT_TYPE_WORKFLOW_TASK_TIMED_OUT:
|
|
// No Operation
|
|
case enumspb.EVENT_TYPE_WORKFLOW_TASK_FAILED:
|
|
// No Operation
|
|
case enumspb.EVENT_TYPE_WORKFLOW_TASK_COMPLETED:
|
|
// No Operation
|
|
case enumspb.EVENT_TYPE_ACTIVITY_TASK_SCHEDULED:
|
|
weh.commandsHelper.handleActivityTaskScheduled(
|
|
event.GetActivityTaskScheduledEventAttributes().GetActivityId(), event.GetEventId())
|
|
|
|
case enumspb.EVENT_TYPE_ACTIVITY_TASK_STARTED:
|
|
// No Operation
|
|
|
|
case enumspb.EVENT_TYPE_ACTIVITY_TASK_COMPLETED:
|
|
err = weh.handleActivityTaskCompleted(event)
|
|
|
|
case enumspb.EVENT_TYPE_ACTIVITY_TASK_FAILED:
|
|
err = weh.handleActivityTaskFailed(event)
|
|
|
|
case enumspb.EVENT_TYPE_ACTIVITY_TASK_TIMED_OUT:
|
|
err = weh.handleActivityTaskTimedOut(event)
|
|
|
|
case enumspb.EVENT_TYPE_ACTIVITY_TASK_CANCEL_REQUESTED:
|
|
weh.commandsHelper.handleActivityTaskCancelRequested(
|
|
event.GetActivityTaskCancelRequestedEventAttributes().GetScheduledEventId())
|
|
|
|
case enumspb.EVENT_TYPE_ACTIVITY_TASK_CANCELED:
|
|
err = weh.handleActivityTaskCanceled(event)
|
|
|
|
case enumspb.EVENT_TYPE_TIMER_STARTED:
|
|
weh.commandsHelper.handleTimerStarted(event.GetTimerStartedEventAttributes().GetTimerId())
|
|
|
|
case enumspb.EVENT_TYPE_TIMER_FIRED:
|
|
weh.handleTimerFired(event)
|
|
|
|
case enumspb.EVENT_TYPE_TIMER_CANCELED:
|
|
weh.commandsHelper.handleTimerCanceled(event.GetTimerCanceledEventAttributes().GetTimerId())
|
|
|
|
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_CANCEL_REQUESTED:
|
|
weh.handleWorkflowExecutionCancelRequested()
|
|
|
|
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_CANCELED:
|
|
// No Operation.
|
|
|
|
case enumspb.EVENT_TYPE_REQUEST_CANCEL_EXTERNAL_WORKFLOW_EXECUTION_INITIATED:
|
|
_ = weh.handleRequestCancelExternalWorkflowExecutionInitiated(event)
|
|
|
|
case enumspb.EVENT_TYPE_REQUEST_CANCEL_EXTERNAL_WORKFLOW_EXECUTION_FAILED:
|
|
_ = weh.handleRequestCancelExternalWorkflowExecutionFailed(event)
|
|
|
|
case enumspb.EVENT_TYPE_EXTERNAL_WORKFLOW_EXECUTION_CANCEL_REQUESTED:
|
|
_ = weh.handleExternalWorkflowExecutionCancelRequested(event)
|
|
|
|
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_CONTINUED_AS_NEW:
|
|
// No Operation.
|
|
|
|
case enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_SIGNALED:
|
|
err = weh.handleWorkflowExecutionSignaled(event.GetWorkflowExecutionSignaledEventAttributes())
|
|
|
|
case enumspb.EVENT_TYPE_SIGNAL_EXTERNAL_WORKFLOW_EXECUTION_INITIATED:
|
|
signalID := event.GetSignalExternalWorkflowExecutionInitiatedEventAttributes().Control
|
|
weh.commandsHelper.handleSignalExternalWorkflowExecutionInitiated(event.GetEventId(), signalID)
|
|
|
|
case enumspb.EVENT_TYPE_SIGNAL_EXTERNAL_WORKFLOW_EXECUTION_FAILED:
|
|
_ = weh.handleSignalExternalWorkflowExecutionFailed(event)
|
|
|
|
case enumspb.EVENT_TYPE_EXTERNAL_WORKFLOW_EXECUTION_SIGNALED:
|
|
_ = weh.handleSignalExternalWorkflowExecutionCompleted(event)
|
|
|
|
case enumspb.EVENT_TYPE_MARKER_RECORDED:
|
|
err = weh.handleMarkerRecorded(event.GetEventId(), event.GetMarkerRecordedEventAttributes())
|
|
|
|
case enumspb.EVENT_TYPE_START_CHILD_WORKFLOW_EXECUTION_INITIATED:
|
|
weh.commandsHelper.handleStartChildWorkflowExecutionInitiated(
|
|
event.GetStartChildWorkflowExecutionInitiatedEventAttributes().GetWorkflowId())
|
|
|
|
case enumspb.EVENT_TYPE_START_CHILD_WORKFLOW_EXECUTION_FAILED:
|
|
err = weh.handleStartChildWorkflowExecutionFailed(event)
|
|
|
|
case enumspb.EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_STARTED:
|
|
err = weh.handleChildWorkflowExecutionStarted(event)
|
|
|
|
case enumspb.EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_COMPLETED:
|
|
err = weh.handleChildWorkflowExecutionCompleted(event)
|
|
|
|
case enumspb.EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_FAILED:
|
|
err = weh.handleChildWorkflowExecutionFailed(event)
|
|
|
|
case enumspb.EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_CANCELED:
|
|
err = weh.handleChildWorkflowExecutionCanceled(event)
|
|
|
|
case enumspb.EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_TIMED_OUT:
|
|
err = weh.handleChildWorkflowExecutionTimedOut(event)
|
|
|
|
case enumspb.EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_TERMINATED:
|
|
err = weh.handleChildWorkflowExecutionTerminated(event)
|
|
|
|
case enumspb.EVENT_TYPE_UPSERT_WORKFLOW_SEARCH_ATTRIBUTES:
|
|
weh.handleUpsertWorkflowSearchAttributes(event)
|
|
|
|
default:
|
|
weh.logger.Error("unknown event type",
|
|
tagEventID, event.GetEventId(),
|
|
tagEventType, event.GetEventType().String())
|
|
// Do not fail to be forward compatible with new events
|
|
}
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// When replaying histories to get stack trace or current state the last event might be not
|
|
// workflow task started. So always call OnWorkflowTaskStarted on the last event.
|
|
// Don't call for EventType_WorkflowTaskStarted as it was already called when handling it.
|
|
if isLast && event.GetEventType() != enumspb.EVENT_TYPE_WORKFLOW_TASK_STARTED {
|
|
weh.workflowDefinition.OnWorkflowTaskStarted(weh.deadlockDetectionTimeout)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (weh *workflowExecutionEventHandlerImpl) ProcessQuery(
|
|
queryType string,
|
|
queryArgs *commonpb.Payloads,
|
|
header *commonpb.Header,
|
|
) (*commonpb.Payloads, error) {
|
|
switch queryType {
|
|
case QueryTypeStackTrace:
|
|
return weh.encodeArg(weh.StackTrace())
|
|
case QueryTypeOpenSessions:
|
|
return weh.encodeArg(weh.getOpenSessions())
|
|
default:
|
|
result, err := weh.queryHandler(queryType, queryArgs, header)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if result.Size() > queryResultSizeLimit {
|
|
weh.logger.Error("Query result size exceeds limit.",
|
|
tagQueryType, queryType,
|
|
tagWorkflowID, weh.workflowInfo.WorkflowExecution.ID,
|
|
tagRunID, weh.workflowInfo.WorkflowExecution.RunID)
|
|
return nil, fmt.Errorf("query result size (%v) exceeds limit (%v)", result.Size(), queryResultSizeLimit)
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
}
|
|
|
|
func (weh *workflowExecutionEventHandlerImpl) StackTrace() string {
|
|
return weh.workflowDefinition.StackTrace()
|
|
}
|
|
|
|
func (weh *workflowExecutionEventHandlerImpl) Close() {
|
|
if weh.workflowDefinition != nil {
|
|
weh.workflowDefinition.Close()
|
|
}
|
|
}
|
|
|
|
func (weh *workflowExecutionEventHandlerImpl) handleWorkflowExecutionStarted(
|
|
attributes *historypb.WorkflowExecutionStartedEventAttributes) (err error) {
|
|
weh.workflowDefinition, err = weh.registry.getWorkflowDefinition(
|
|
weh.workflowInfo.WorkflowType,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Invoke the workflow.
|
|
weh.workflowDefinition.Execute(weh, attributes.Header, attributes.Input)
|
|
return nil
|
|
}
|
|
|
|
func (weh *workflowExecutionEventHandlerImpl) handleActivityTaskCompleted(event *historypb.HistoryEvent) error {
|
|
activityID, scheduledEventID := weh.commandsHelper.getActivityAndScheduledEventIDs(event)
|
|
command := weh.commandsHelper.handleActivityTaskClosed(activityID, scheduledEventID)
|
|
activity := command.getData().(*scheduledActivity)
|
|
if activity.handled {
|
|
return nil
|
|
}
|
|
activity.handle(event.GetActivityTaskCompletedEventAttributes().Result, nil)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (weh *workflowExecutionEventHandlerImpl) handleActivityTaskFailed(event *historypb.HistoryEvent) error {
|
|
activityID, scheduledEventID := weh.commandsHelper.getActivityAndScheduledEventIDs(event)
|
|
command := weh.commandsHelper.handleActivityTaskClosed(activityID, scheduledEventID)
|
|
activity := command.getData().(*scheduledActivity)
|
|
if activity.handled {
|
|
return nil
|
|
}
|
|
|
|
attributes := event.GetActivityTaskFailedEventAttributes()
|
|
activityTaskErr := NewActivityError(
|
|
attributes.GetScheduledEventId(),
|
|
attributes.GetStartedEventId(),
|
|
attributes.GetIdentity(),
|
|
&commonpb.ActivityType{Name: activity.activityType.Name},
|
|
activityID,
|
|
attributes.GetRetryState(),
|
|
ConvertFailureToError(attributes.GetFailure(), weh.GetDataConverter()),
|
|
)
|
|
|
|
activity.handle(nil, activityTaskErr)
|
|
return nil
|
|
}
|
|
|
|
func (weh *workflowExecutionEventHandlerImpl) handleActivityTaskTimedOut(event *historypb.HistoryEvent) error {
|
|
activityID, scheduledEventID := weh.commandsHelper.getActivityAndScheduledEventIDs(event)
|
|
command := weh.commandsHelper.handleActivityTaskClosed(activityID, scheduledEventID)
|
|
activity := command.getData().(*scheduledActivity)
|
|
if activity.handled {
|
|
return nil
|
|
}
|
|
|
|
attributes := event.GetActivityTaskTimedOutEventAttributes()
|
|
timeoutError := ConvertFailureToError(attributes.GetFailure(), weh.GetDataConverter())
|
|
|
|
activityTaskErr := NewActivityError(
|
|
attributes.GetScheduledEventId(),
|
|
attributes.GetStartedEventId(),
|
|
"",
|
|
&commonpb.ActivityType{Name: activity.activityType.Name},
|
|
activityID,
|
|
attributes.GetRetryState(),
|
|
timeoutError,
|
|
)
|
|
|
|
activity.handle(nil, activityTaskErr)
|
|
return nil
|
|
}
|
|
|
|
func (weh *workflowExecutionEventHandlerImpl) handleActivityTaskCanceled(event *historypb.HistoryEvent) error {
|
|
activityID, scheduledEventID := weh.commandsHelper.getActivityAndScheduledEventIDs(event)
|
|
command := weh.commandsHelper.handleActivityTaskCanceled(activityID, scheduledEventID)
|
|
activity := command.getData().(*scheduledActivity)
|
|
if activity.handled {
|
|
return nil
|
|
}
|
|
|
|
if command.isDone() || !activity.waitForCancelRequest {
|
|
// Clear this so we don't have a recursive call that while executing might call the cancel one.
|
|
|
|
attributes := event.GetActivityTaskCanceledEventAttributes()
|
|
details := newEncodedValues(attributes.GetDetails(), weh.GetDataConverter())
|
|
|
|
activityTaskErr := NewActivityError(
|
|
attributes.GetScheduledEventId(),
|
|
attributes.GetStartedEventId(),
|
|
attributes.GetIdentity(),
|
|
&commonpb.ActivityType{Name: activity.activityType.Name},
|
|
activityID,
|
|
enumspb.RETRY_STATE_NON_RETRYABLE_FAILURE,
|
|
NewCanceledError(details),
|
|
)
|
|
|
|
activity.handle(nil, activityTaskErr)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (weh *workflowExecutionEventHandlerImpl) handleTimerFired(event *historypb.HistoryEvent) {
|
|
timerID := event.GetTimerFiredEventAttributes().GetTimerId()
|
|
command := weh.commandsHelper.handleTimerClosed(timerID)
|
|
timer := command.getData().(*scheduledTimer)
|
|
if timer.handled {
|
|
return
|
|
}
|
|
|
|
timer.handle(nil, nil)
|
|
}
|
|
|
|
func (weh *workflowExecutionEventHandlerImpl) handleWorkflowExecutionCancelRequested() {
|
|
weh.cancelHandler()
|
|
}
|
|
|
|
func (weh *workflowExecutionEventHandlerImpl) handleMarkerRecorded(
|
|
eventID int64,
|
|
attributes *historypb.MarkerRecordedEventAttributes,
|
|
) error {
|
|
var err error
|
|
if attributes.GetDetails() == nil {
|
|
err = ErrMissingMarkerDetails
|
|
} else {
|
|
switch attributes.GetMarkerName() {
|
|
case sideEffectMarkerName:
|
|
if sideEffectIDPayload, ok := attributes.GetDetails()[sideEffectMarkerIDName]; !ok {
|
|
err = fmt.Errorf("key %q: %w", sideEffectMarkerIDName, ErrMissingMarkerDataKey)
|
|
} else {
|
|
if sideEffectData, ok := attributes.GetDetails()[sideEffectMarkerDataName]; !ok {
|
|
err = fmt.Errorf("key %q: %w", sideEffectMarkerDataName, ErrMissingMarkerDataKey)
|
|
} else {
|
|
var sideEffectID int64
|
|
_ = weh.dataConverter.FromPayloads(sideEffectIDPayload, &sideEffectID)
|
|
weh.sideEffectResult[sideEffectID] = sideEffectData
|
|
}
|
|
}
|
|
case versionMarkerName:
|
|
if changeIDPayload, ok := attributes.GetDetails()[versionMarkerChangeIDName]; !ok {
|
|
err = fmt.Errorf("key %q: %w", versionMarkerChangeIDName, ErrMissingMarkerDataKey)
|
|
} else {
|
|
if versionPayload, ok := attributes.GetDetails()[versionMarkerDataName]; !ok {
|
|
err = fmt.Errorf("key %q: %w", versionMarkerDataName, ErrMissingMarkerDataKey)
|
|
} else {
|
|
var changeID string
|
|
_ = weh.dataConverter.FromPayloads(changeIDPayload, &changeID)
|
|
var version Version
|
|
_ = weh.dataConverter.FromPayloads(versionPayload, &version)
|
|
weh.changeVersions[changeID] = version
|
|
weh.commandsHelper.handleVersionMarker(eventID, changeID)
|
|
}
|
|
}
|
|
case localActivityMarkerName:
|
|
err = weh.handleLocalActivityMarker(attributes.GetDetails(), attributes.GetFailure())
|
|
case mutableSideEffectMarkerName:
|
|
if sideEffectIDPayload, ok := attributes.GetDetails()[sideEffectMarkerIDName]; !ok {
|
|
err = fmt.Errorf("key %q: %w", sideEffectMarkerIDName, ErrMissingMarkerDataKey)
|
|
} else {
|
|
if sideEffectData, ok := attributes.GetDetails()[sideEffectMarkerDataName]; !ok {
|
|
err = fmt.Errorf("key %q: %w", sideEffectMarkerDataName, ErrMissingMarkerDataKey)
|
|
} else {
|
|
var sideEffectID string
|
|
_ = weh.dataConverter.FromPayloads(sideEffectIDPayload, &sideEffectID)
|
|
weh.mutableSideEffect[sideEffectID] = sideEffectData
|
|
}
|
|
}
|
|
default:
|
|
err = ErrUnknownMarkerName
|
|
}
|
|
}
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("marker name %q for eventId %d: %w", attributes.GetMarkerName(), eventID, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (weh *workflowExecutionEventHandlerImpl) handleLocalActivityMarker(details map[string]*commonpb.Payloads, failure *failurepb.Failure) error {
|
|
var markerData *commonpb.Payloads
|
|
var ok bool
|
|
if markerData, ok = details[localActivityMarkerDataName]; !ok {
|
|
return fmt.Errorf("key %q: %w", localActivityMarkerDataName, ErrMissingMarkerDataKey)
|
|
}
|
|
|
|
lamd := localActivityMarkerData{}
|
|
if err := weh.dataConverter.FromPayloads(markerData, &lamd); err != nil {
|
|
return err
|
|
}
|
|
|
|
if la, ok := weh.pendingLaTasks[lamd.ActivityID]; ok {
|
|
if len(lamd.ActivityType) > 0 && lamd.ActivityType != la.params.ActivityType {
|
|
// history marker mismatch to the current code.
|
|
panicMsg := fmt.Sprintf("code execute local activity %v, but history event found %v, markerData: %v", la.params.ActivityType, lamd.ActivityType, markerData)
|
|
panicIllegalState(panicMsg)
|
|
}
|
|
weh.commandsHelper.recordLocalActivityMarker(lamd.ActivityID, details, failure)
|
|
delete(weh.pendingLaTasks, lamd.ActivityID)
|
|
delete(weh.unstartedLaTasks, lamd.ActivityID)
|
|
lar := &LocalActivityResultWrapper{}
|
|
if failure != nil {
|
|
lar.Attempt = lamd.Attempt
|
|
lar.Backoff = lamd.Backoff
|
|
lar.Err = ConvertFailureToError(failure, weh.GetDataConverter())
|
|
} else {
|
|
// Result might not be there if local activity doesn't have return value.
|
|
lar.Result = details[localActivityResultName]
|
|
}
|
|
la.callback(lar)
|
|
|
|
// update time
|
|
weh.SetCurrentReplayTime(lamd.ReplayTime)
|
|
|
|
// resume workflow execution after apply local activity result
|
|
weh.workflowDefinition.OnWorkflowTaskStarted(weh.deadlockDetectionTimeout)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (weh *workflowExecutionEventHandlerImpl) ProcessLocalActivityResult(lar *localActivityResult) error {
|
|
details := make(map[string]*commonpb.Payloads)
|
|
|
|
// convert local activity result and error to marker data
|
|
lamd := localActivityMarkerData{
|
|
ActivityID: lar.task.activityID,
|
|
ActivityType: lar.task.params.ActivityType,
|
|
ReplayTime: weh.currentReplayTime.Add(time.Since(weh.currentLocalTime)),
|
|
Attempt: lar.task.attempt,
|
|
}
|
|
if lar.err != nil {
|
|
lamd.Backoff = lar.backoff
|
|
} else if lar.result != nil {
|
|
details[localActivityResultName] = lar.result
|
|
}
|
|
|
|
// encode marker data
|
|
markerData, err := weh.encodeArg(lamd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
details[localActivityMarkerDataName] = markerData
|
|
|
|
// create marker event for local activity result
|
|
markerEvent := &historypb.HistoryEvent{
|
|
EventType: enumspb.EVENT_TYPE_MARKER_RECORDED,
|
|
Attributes: &historypb.HistoryEvent_MarkerRecordedEventAttributes{MarkerRecordedEventAttributes: &historypb.MarkerRecordedEventAttributes{
|
|
MarkerName: localActivityMarkerName,
|
|
Failure: ConvertErrorToFailure(lar.err, weh.GetDataConverter()),
|
|
Details: details,
|
|
}},
|
|
}
|
|
|
|
// apply the local activity result to workflow
|
|
return weh.ProcessEvent(markerEvent, false, false)
|
|
}
|
|
|
|
func (weh *workflowExecutionEventHandlerImpl) handleWorkflowExecutionSignaled(
|
|
attributes *historypb.WorkflowExecutionSignaledEventAttributes) error {
|
|
return weh.signalHandler(attributes.GetSignalName(), attributes.Input, attributes.Header)
|
|
}
|
|
|
|
func (weh *workflowExecutionEventHandlerImpl) handleStartChildWorkflowExecutionFailed(event *historypb.HistoryEvent) error {
|
|
attributes := event.GetStartChildWorkflowExecutionFailedEventAttributes()
|
|
childWorkflowID := attributes.GetWorkflowId()
|
|
command := weh.commandsHelper.handleStartChildWorkflowExecutionFailed(childWorkflowID)
|
|
childWorkflow := command.getData().(*scheduledChildWorkflow)
|
|
if childWorkflow.handled {
|
|
return nil
|
|
}
|
|
|
|
if attributes.GetCause() == enumspb.START_CHILD_WORKFLOW_EXECUTION_FAILED_CAUSE_WORKFLOW_ALREADY_EXISTS {
|
|
err := NewChildWorkflowExecutionError(
|
|
attributes.GetNamespace(),
|
|
attributes.GetWorkflowId(),
|
|
"",
|
|
attributes.GetWorkflowType().GetName(),
|
|
attributes.GetInitiatedEventId(),
|
|
0,
|
|
enumspb.RETRY_STATE_NON_RETRYABLE_FAILURE,
|
|
&ChildWorkflowExecutionAlreadyStartedError{},
|
|
)
|
|
childWorkflow.handleFailedToStart(nil, err)
|
|
return nil
|
|
}
|
|
|
|
return fmt.Errorf("unknown cause: %v", attributes.GetCause())
|
|
}
|
|
|
|
func (weh *workflowExecutionEventHandlerImpl) handleChildWorkflowExecutionStarted(event *historypb.HistoryEvent) error {
|
|
attributes := event.GetChildWorkflowExecutionStartedEventAttributes()
|
|
childWorkflowID := attributes.WorkflowExecution.GetWorkflowId()
|
|
childRunID := attributes.WorkflowExecution.GetRunId()
|
|
command := weh.commandsHelper.handleChildWorkflowExecutionStarted(childWorkflowID)
|
|
childWorkflow := command.getData().(*scheduledChildWorkflow)
|
|
if childWorkflow.handled {
|
|
return nil
|
|
}
|
|
|
|
childWorkflowExecution := WorkflowExecution{
|
|
ID: childWorkflowID,
|
|
RunID: childRunID,
|
|
}
|
|
childWorkflow.startedCallback(childWorkflowExecution, nil)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (weh *workflowExecutionEventHandlerImpl) handleChildWorkflowExecutionCompleted(event *historypb.HistoryEvent) error {
|
|
attributes := event.GetChildWorkflowExecutionCompletedEventAttributes()
|
|
childWorkflowID := attributes.WorkflowExecution.GetWorkflowId()
|
|
command := weh.commandsHelper.handleChildWorkflowExecutionClosed(childWorkflowID)
|
|
childWorkflow := command.getData().(*scheduledChildWorkflow)
|
|
if childWorkflow.handled {
|
|
return nil
|
|
}
|
|
childWorkflow.handle(attributes.Result, nil)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (weh *workflowExecutionEventHandlerImpl) handleChildWorkflowExecutionFailed(event *historypb.HistoryEvent) error {
|
|
attributes := event.GetChildWorkflowExecutionFailedEventAttributes()
|
|
childWorkflowID := attributes.WorkflowExecution.GetWorkflowId()
|
|
command := weh.commandsHelper.handleChildWorkflowExecutionClosed(childWorkflowID)
|
|
childWorkflow := command.getData().(*scheduledChildWorkflow)
|
|
if childWorkflow.handled {
|
|
return nil
|
|
}
|
|
|
|
childWorkflowExecutionError := NewChildWorkflowExecutionError(
|
|
attributes.GetNamespace(),
|
|
attributes.GetWorkflowExecution().GetWorkflowId(),
|
|
attributes.GetWorkflowExecution().GetRunId(),
|
|
attributes.GetWorkflowType().GetName(),
|
|
attributes.GetInitiatedEventId(),
|
|
attributes.GetStartedEventId(),
|
|
attributes.GetRetryState(),
|
|
ConvertFailureToError(attributes.GetFailure(), weh.GetDataConverter()),
|
|
)
|
|
childWorkflow.handle(nil, childWorkflowExecutionError)
|
|
return nil
|
|
}
|
|
|
|
func (weh *workflowExecutionEventHandlerImpl) handleChildWorkflowExecutionCanceled(event *historypb.HistoryEvent) error {
|
|
attributes := event.GetChildWorkflowExecutionCanceledEventAttributes()
|
|
childWorkflowID := attributes.WorkflowExecution.GetWorkflowId()
|
|
command := weh.commandsHelper.handleChildWorkflowExecutionCanceled(childWorkflowID)
|
|
childWorkflow := command.getData().(*scheduledChildWorkflow)
|
|
if childWorkflow.handled {
|
|
return nil
|
|
}
|
|
details := newEncodedValues(attributes.Details, weh.GetDataConverter())
|
|
|
|
childWorkflowExecutionError := NewChildWorkflowExecutionError(
|
|
attributes.GetNamespace(),
|
|
attributes.GetWorkflowExecution().GetWorkflowId(),
|
|
attributes.GetWorkflowExecution().GetRunId(),
|
|
attributes.GetWorkflowType().GetName(),
|
|
attributes.GetInitiatedEventId(),
|
|
attributes.GetStartedEventId(),
|
|
enumspb.RETRY_STATE_NON_RETRYABLE_FAILURE,
|
|
NewCanceledError(details),
|
|
)
|
|
childWorkflow.handle(nil, childWorkflowExecutionError)
|
|
return nil
|
|
}
|
|
|
|
func (weh *workflowExecutionEventHandlerImpl) handleChildWorkflowExecutionTimedOut(event *historypb.HistoryEvent) error {
|
|
attributes := event.GetChildWorkflowExecutionTimedOutEventAttributes()
|
|
childWorkflowID := attributes.WorkflowExecution.GetWorkflowId()
|
|
command := weh.commandsHelper.handleChildWorkflowExecutionClosed(childWorkflowID)
|
|
childWorkflow := command.getData().(*scheduledChildWorkflow)
|
|
if childWorkflow.handled {
|
|
return nil
|
|
}
|
|
|
|
childWorkflowExecutionError := NewChildWorkflowExecutionError(
|
|
attributes.GetNamespace(),
|
|
attributes.GetWorkflowExecution().GetWorkflowId(),
|
|
attributes.GetWorkflowExecution().GetRunId(),
|
|
attributes.GetWorkflowType().GetName(),
|
|
attributes.GetInitiatedEventId(),
|
|
attributes.GetStartedEventId(),
|
|
attributes.GetRetryState(),
|
|
NewTimeoutError("Child workflow timeout", enumspb.TIMEOUT_TYPE_START_TO_CLOSE, nil),
|
|
)
|
|
childWorkflow.handle(nil, childWorkflowExecutionError)
|
|
return nil
|
|
}
|
|
|
|
func (weh *workflowExecutionEventHandlerImpl) handleChildWorkflowExecutionTerminated(event *historypb.HistoryEvent) error {
|
|
attributes := event.GetChildWorkflowExecutionTerminatedEventAttributes()
|
|
childWorkflowID := attributes.WorkflowExecution.GetWorkflowId()
|
|
command := weh.commandsHelper.handleChildWorkflowExecutionClosed(childWorkflowID)
|
|
childWorkflow := command.getData().(*scheduledChildWorkflow)
|
|
if childWorkflow.handled {
|
|
return nil
|
|
}
|
|
|
|
childWorkflowExecutionError := NewChildWorkflowExecutionError(
|
|
attributes.GetNamespace(),
|
|
attributes.GetWorkflowExecution().GetWorkflowId(),
|
|
attributes.GetWorkflowExecution().GetRunId(),
|
|
attributes.GetWorkflowType().GetName(),
|
|
attributes.GetInitiatedEventId(),
|
|
attributes.GetStartedEventId(),
|
|
enumspb.RETRY_STATE_NON_RETRYABLE_FAILURE,
|
|
newTerminatedError(),
|
|
)
|
|
childWorkflow.handle(nil, childWorkflowExecutionError)
|
|
return nil
|
|
}
|
|
|
|
func (weh *workflowExecutionEventHandlerImpl) handleUpsertWorkflowSearchAttributes(event *historypb.HistoryEvent) {
|
|
weh.updateWorkflowInfoWithSearchAttributes(event.GetUpsertWorkflowSearchAttributesEventAttributes().SearchAttributes)
|
|
}
|
|
|
|
func (weh *workflowExecutionEventHandlerImpl) handleRequestCancelExternalWorkflowExecutionInitiated(event *historypb.HistoryEvent) error {
|
|
// For cancellation of child workflow only, we do not use cancellation ID
|
|
// for cancellation of external workflow, we have to use cancellation ID
|
|
attribute := event.GetRequestCancelExternalWorkflowExecutionInitiatedEventAttributes()
|
|
workflowID := attribute.WorkflowExecution.GetWorkflowId()
|
|
cancellationID := attribute.Control
|
|
weh.commandsHelper.handleRequestCancelExternalWorkflowExecutionInitiated(event.GetEventId(), workflowID, cancellationID)
|
|
return nil
|
|
}
|
|
|
|
func (weh *workflowExecutionEventHandlerImpl) handleExternalWorkflowExecutionCancelRequested(event *historypb.HistoryEvent) error {
|
|
// For cancellation of child workflow only, we do not use cancellation ID
|
|
// for cancellation of external workflow, we have to use cancellation ID
|
|
attributes := event.GetExternalWorkflowExecutionCancelRequestedEventAttributes()
|
|
workflowID := attributes.WorkflowExecution.GetWorkflowId()
|
|
isExternal, command := weh.commandsHelper.handleExternalWorkflowExecutionCancelRequested(attributes.GetInitiatedEventId(), workflowID)
|
|
if isExternal {
|
|
// for cancel external workflow, we need to set the future
|
|
cancellation := command.getData().(*scheduledCancellation)
|
|
if cancellation.handled {
|
|
return nil
|
|
}
|
|
cancellation.handle(nil, nil)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (weh *workflowExecutionEventHandlerImpl) handleRequestCancelExternalWorkflowExecutionFailed(event *historypb.HistoryEvent) error {
|
|
// For cancellation of child workflow only, we do not use cancellation ID
|
|
// for cancellation of external workflow, we have to use cancellation ID
|
|
attributes := event.GetRequestCancelExternalWorkflowExecutionFailedEventAttributes()
|
|
workflowID := attributes.WorkflowExecution.GetWorkflowId()
|
|
isExternal, command := weh.commandsHelper.handleRequestCancelExternalWorkflowExecutionFailed(attributes.GetInitiatedEventId(), workflowID)
|
|
if isExternal {
|
|
// for cancel external workflow, we need to set the future
|
|
cancellation := command.getData().(*scheduledCancellation)
|
|
if cancellation.handled {
|
|
return nil
|
|
}
|
|
err := fmt.Errorf("cancel external workflow failed, %v", attributes.GetCause())
|
|
cancellation.handle(nil, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (weh *workflowExecutionEventHandlerImpl) handleSignalExternalWorkflowExecutionCompleted(event *historypb.HistoryEvent) error {
|
|
attributes := event.GetExternalWorkflowExecutionSignaledEventAttributes()
|
|
command := weh.commandsHelper.handleSignalExternalWorkflowExecutionCompleted(attributes.GetInitiatedEventId())
|
|
signal := command.getData().(*scheduledSignal)
|
|
if signal.handled {
|
|
return nil
|
|
}
|
|
signal.handle(nil, nil)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (weh *workflowExecutionEventHandlerImpl) handleSignalExternalWorkflowExecutionFailed(event *historypb.HistoryEvent) error {
|
|
attributes := event.GetSignalExternalWorkflowExecutionFailedEventAttributes()
|
|
command := weh.commandsHelper.handleSignalExternalWorkflowExecutionFailed(attributes.GetInitiatedEventId())
|
|
signal := command.getData().(*scheduledSignal)
|
|
if signal.handled {
|
|
return nil
|
|
}
|
|
|
|
var err error
|
|
switch attributes.GetCause() {
|
|
case enumspb.SIGNAL_EXTERNAL_WORKFLOW_EXECUTION_FAILED_CAUSE_EXTERNAL_WORKFLOW_EXECUTION_NOT_FOUND:
|
|
err = newUnknownExternalWorkflowExecutionError()
|
|
default:
|
|
err = fmt.Errorf("signal external workflow failed, %v", attributes.GetCause())
|
|
}
|
|
|
|
signal.handle(nil, err)
|
|
|
|
return nil
|
|
}
|