peridot/vendor/go.temporal.io/sdk/internal/internal_event_handlers.go
2022-07-07 22:13:21 +02:00

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 = &params.WorkflowExecutionTimeout
attributes.WorkflowRunTimeout = &params.WorkflowRunTimeout
attributes.WorkflowTaskTimeout = &params.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 = &parameters.ScheduleToCloseTimeout
scheduleTaskAttr.StartToCloseTimeout = &parameters.StartToCloseTimeout
scheduleTaskAttr.ScheduleToStartTimeout = &parameters.ScheduleToStartTimeout
scheduleTaskAttr.HeartbeatTimeout = &parameters.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: &params,
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
}