mirror of
https://github.com/rocky-linux/peridot.git
synced 2024-10-19 07:55:07 +00:00
1361 lines
49 KiB
Go
1361 lines
49 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
|
||
|
|
||
|
import (
|
||
|
"container/list"
|
||
|
"fmt"
|
||
|
"strconv"
|
||
|
|
||
|
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"
|
||
|
|
||
|
"go.temporal.io/sdk/converter"
|
||
|
"go.temporal.io/sdk/internal/common/util"
|
||
|
)
|
||
|
|
||
|
type (
|
||
|
commandState int32
|
||
|
commandType int32
|
||
|
|
||
|
commandID struct {
|
||
|
commandType commandType
|
||
|
id string
|
||
|
}
|
||
|
|
||
|
commandStateMachine interface {
|
||
|
getState() commandState
|
||
|
getID() commandID
|
||
|
isDone() bool
|
||
|
getCommand() *commandpb.Command // return nil if there is no command in current state
|
||
|
cancel()
|
||
|
|
||
|
handleStartedEvent()
|
||
|
handleCancelInitiatedEvent()
|
||
|
handleCanceledEvent()
|
||
|
handleCancelFailedEvent()
|
||
|
handleCompletionEvent()
|
||
|
handleInitiationFailedEvent()
|
||
|
handleInitiatedEvent()
|
||
|
|
||
|
handleCommandSent()
|
||
|
|
||
|
setData(data interface{})
|
||
|
getData() interface{}
|
||
|
}
|
||
|
|
||
|
commandStateMachineBase struct {
|
||
|
id commandID
|
||
|
state commandState
|
||
|
history []string
|
||
|
data interface{}
|
||
|
helper *commandsHelper
|
||
|
}
|
||
|
|
||
|
activityCommandStateMachine struct {
|
||
|
*commandStateMachineBase
|
||
|
scheduleID int64
|
||
|
attributes *commandpb.ScheduleActivityTaskCommandAttributes
|
||
|
}
|
||
|
|
||
|
cancelActivityStateMachine struct {
|
||
|
*commandStateMachineBase
|
||
|
attributes *commandpb.RequestCancelActivityTaskCommandAttributes
|
||
|
|
||
|
// The commandsHelper.nextCommandEventIDResetCounter when this command
|
||
|
// incremented commandsHelper.commandsCancelledDuringWFCancellation.
|
||
|
cancelledOnEventIDResetCounter uint64
|
||
|
}
|
||
|
|
||
|
timerCommandStateMachine struct {
|
||
|
*commandStateMachineBase
|
||
|
attributes *commandpb.StartTimerCommandAttributes
|
||
|
}
|
||
|
|
||
|
cancelTimerCommandStateMachine struct {
|
||
|
*commandStateMachineBase
|
||
|
attributes *commandpb.CancelTimerCommandAttributes
|
||
|
|
||
|
// The commandsHelper.nextCommandEventIDResetCounter when this command
|
||
|
// incremented commandsHelper.commandsCancelledDuringWFCancellation.
|
||
|
cancelledOnEventIDResetCounter uint64
|
||
|
}
|
||
|
|
||
|
childWorkflowCommandStateMachine struct {
|
||
|
*commandStateMachineBase
|
||
|
attributes *commandpb.StartChildWorkflowExecutionCommandAttributes
|
||
|
}
|
||
|
|
||
|
naiveCommandStateMachine struct {
|
||
|
*commandStateMachineBase
|
||
|
command *commandpb.Command
|
||
|
}
|
||
|
|
||
|
// only possible state transition is: CREATED->SENT->INITIATED->COMPLETED
|
||
|
cancelExternalWorkflowCommandStateMachine struct {
|
||
|
*naiveCommandStateMachine
|
||
|
}
|
||
|
|
||
|
signalExternalWorkflowCommandStateMachine struct {
|
||
|
*naiveCommandStateMachine
|
||
|
}
|
||
|
|
||
|
// only possible state transition is: CREATED->SENT->COMPLETED
|
||
|
markerCommandStateMachine struct {
|
||
|
*naiveCommandStateMachine
|
||
|
}
|
||
|
|
||
|
upsertSearchAttributesCommandStateMachine struct {
|
||
|
*naiveCommandStateMachine
|
||
|
}
|
||
|
|
||
|
commandsHelper struct {
|
||
|
nextCommandEventID int64
|
||
|
orderedCommands *list.List
|
||
|
commands map[commandID]*list.Element
|
||
|
|
||
|
scheduledEventIDToActivityID map[int64]string
|
||
|
scheduledEventIDToCancellationID map[int64]string
|
||
|
scheduledEventIDToSignalID map[int64]string
|
||
|
versionMarkerLookup map[int64]string
|
||
|
commandsCancelledDuringWFCancellation int64
|
||
|
workflowExecutionIsCancelling bool
|
||
|
|
||
|
// Incremented everytime nextCommandEventID and
|
||
|
// commandsCancelledDuringWFCancellation is reset (i.e. on new workflow
|
||
|
// task). Won't ever happen, but technically the way this value is compared
|
||
|
// is safe for overflow wrap around.
|
||
|
nextCommandEventIDResetCounter uint64
|
||
|
}
|
||
|
|
||
|
// panic when command state machine is in illegal state
|
||
|
stateMachineIllegalStatePanic struct {
|
||
|
message string
|
||
|
}
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
commandStateCreated commandState = 0
|
||
|
commandStateCommandSent commandState = 1
|
||
|
commandStateCanceledBeforeInitiated commandState = 2
|
||
|
commandStateInitiated commandState = 3
|
||
|
commandStateStarted commandState = 4
|
||
|
commandStateCanceledAfterInitiated commandState = 5
|
||
|
commandStateCanceledAfterStarted commandState = 6
|
||
|
commandStateCancellationCommandSent commandState = 7
|
||
|
commandStateCompletedAfterCancellationCommandSent commandState = 8
|
||
|
commandStateCompleted commandState = 9
|
||
|
commandStateCanceledBeforeSent commandState = 10
|
||
|
commandStateCancellationCommandAccepted commandState = 11
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
commandTypeActivity commandType = 0
|
||
|
commandTypeChildWorkflow commandType = 1
|
||
|
commandTypeCancellation commandType = 2
|
||
|
commandTypeMarker commandType = 3
|
||
|
commandTypeTimer commandType = 4
|
||
|
commandTypeSignal commandType = 5
|
||
|
commandTypeUpsertSearchAttributes commandType = 6
|
||
|
commandTypeCancelTimer commandType = 7
|
||
|
commandTypeRequestCancelActivityTask commandType = 8
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
eventCancel = "cancel"
|
||
|
eventCommandSent = "handleCommandSent"
|
||
|
eventInitiated = "handleInitiatedEvent"
|
||
|
eventInitiationFailed = "handleInitiationFailedEvent"
|
||
|
eventStarted = "handleStartedEvent"
|
||
|
eventCompletion = "handleCompletionEvent"
|
||
|
eventCancelInitiated = "handleCancelInitiatedEvent"
|
||
|
eventCancelFailed = "handleCancelFailedEvent"
|
||
|
eventCanceled = "handleCanceledEvent"
|
||
|
eventExternalWorkflowExecutionCancelRequested = "handleExternalWorkflowExecutionCancelRequested"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
sideEffectMarkerName = "SideEffect"
|
||
|
versionMarkerName = "Version"
|
||
|
localActivityMarkerName = "LocalActivity"
|
||
|
mutableSideEffectMarkerName = "MutableSideEffect"
|
||
|
|
||
|
sideEffectMarkerIDName = "side-effect-id"
|
||
|
sideEffectMarkerDataName = "data"
|
||
|
versionMarkerChangeIDName = "change-id"
|
||
|
versionMarkerDataName = "version"
|
||
|
localActivityMarkerDataName = "data"
|
||
|
localActivityResultName = "result"
|
||
|
)
|
||
|
|
||
|
func (d commandState) String() string {
|
||
|
switch d {
|
||
|
case commandStateCreated:
|
||
|
return "Created"
|
||
|
case commandStateCommandSent:
|
||
|
return "CommandSent"
|
||
|
case commandStateCanceledBeforeInitiated:
|
||
|
return "CanceledBeforeInitiated"
|
||
|
case commandStateInitiated:
|
||
|
return "Initiated"
|
||
|
case commandStateStarted:
|
||
|
return "Started"
|
||
|
case commandStateCanceledAfterInitiated:
|
||
|
return "CanceledAfterInitiated"
|
||
|
case commandStateCanceledAfterStarted:
|
||
|
return "CanceledAfterStarted"
|
||
|
case commandStateCancellationCommandSent:
|
||
|
return "CancellationCommandSent"
|
||
|
case commandStateCompletedAfterCancellationCommandSent:
|
||
|
return "CompletedAfterCancellationCommandSent"
|
||
|
case commandStateCompleted:
|
||
|
return "Completed"
|
||
|
case commandStateCanceledBeforeSent:
|
||
|
return "CanceledBeforeSent"
|
||
|
case commandStateCancellationCommandAccepted:
|
||
|
return "CancellationCommandAccepted"
|
||
|
default:
|
||
|
return fmt.Sprintf("Unknown: %d", int32(d))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (d commandType) String() string {
|
||
|
switch d {
|
||
|
case commandTypeActivity:
|
||
|
return "Activity"
|
||
|
case commandTypeChildWorkflow:
|
||
|
return "ChildWorkflow"
|
||
|
case commandTypeCancellation:
|
||
|
return "Cancellation"
|
||
|
case commandTypeMarker:
|
||
|
return "Marker"
|
||
|
case commandTypeTimer:
|
||
|
return "Timer"
|
||
|
case commandTypeSignal:
|
||
|
return "Signal"
|
||
|
case commandTypeCancelTimer:
|
||
|
return "CancelTimer"
|
||
|
case commandTypeRequestCancelActivityTask:
|
||
|
return "RequestCancelActivityTask"
|
||
|
default:
|
||
|
return "Unknown"
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (d commandID) String() string {
|
||
|
return fmt.Sprintf("CommandType: %v, ID: %v", d.commandType, d.id)
|
||
|
}
|
||
|
|
||
|
func makeCommandID(commandType commandType, id string) commandID {
|
||
|
return commandID{commandType: commandType, id: id}
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) newCommandStateMachineBase(commandType commandType, id string) *commandStateMachineBase {
|
||
|
return &commandStateMachineBase{
|
||
|
id: makeCommandID(commandType, id),
|
||
|
state: commandStateCreated,
|
||
|
history: []string{commandStateCreated.String()},
|
||
|
helper: h,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) newActivityCommandStateMachine(
|
||
|
scheduleID int64,
|
||
|
attributes *commandpb.ScheduleActivityTaskCommandAttributes,
|
||
|
) *activityCommandStateMachine {
|
||
|
base := h.newCommandStateMachineBase(commandTypeActivity, attributes.GetActivityId())
|
||
|
return &activityCommandStateMachine{
|
||
|
commandStateMachineBase: base,
|
||
|
scheduleID: scheduleID,
|
||
|
attributes: attributes,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) newCancelActivityStateMachine(attributes *commandpb.RequestCancelActivityTaskCommandAttributes) *cancelActivityStateMachine {
|
||
|
base := h.newCommandStateMachineBase(commandTypeRequestCancelActivityTask, strconv.FormatInt(attributes.GetScheduledEventId(), 10))
|
||
|
return &cancelActivityStateMachine{
|
||
|
commandStateMachineBase: base,
|
||
|
attributes: attributes,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) newTimerCommandStateMachine(attributes *commandpb.StartTimerCommandAttributes) *timerCommandStateMachine {
|
||
|
base := h.newCommandStateMachineBase(commandTypeTimer, attributes.GetTimerId())
|
||
|
return &timerCommandStateMachine{
|
||
|
commandStateMachineBase: base,
|
||
|
attributes: attributes,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) newCancelTimerCommandStateMachine(attributes *commandpb.CancelTimerCommandAttributes) *cancelTimerCommandStateMachine {
|
||
|
base := h.newCommandStateMachineBase(commandTypeCancelTimer, attributes.GetTimerId())
|
||
|
return &cancelTimerCommandStateMachine{
|
||
|
commandStateMachineBase: base,
|
||
|
attributes: attributes,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) newChildWorkflowCommandStateMachine(attributes *commandpb.StartChildWorkflowExecutionCommandAttributes) *childWorkflowCommandStateMachine {
|
||
|
base := h.newCommandStateMachineBase(commandTypeChildWorkflow, attributes.GetWorkflowId())
|
||
|
return &childWorkflowCommandStateMachine{
|
||
|
commandStateMachineBase: base,
|
||
|
attributes: attributes,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) newNaiveCommandStateMachine(commandType commandType, id string, command *commandpb.Command) *naiveCommandStateMachine {
|
||
|
base := h.newCommandStateMachineBase(commandType, id)
|
||
|
return &naiveCommandStateMachine{
|
||
|
commandStateMachineBase: base,
|
||
|
command: command,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) newMarkerCommandStateMachine(id string, attributes *commandpb.RecordMarkerCommandAttributes) *markerCommandStateMachine {
|
||
|
d := createNewCommand(enumspb.COMMAND_TYPE_RECORD_MARKER)
|
||
|
d.Attributes = &commandpb.Command_RecordMarkerCommandAttributes{RecordMarkerCommandAttributes: attributes}
|
||
|
return &markerCommandStateMachine{
|
||
|
naiveCommandStateMachine: h.newNaiveCommandStateMachine(commandTypeMarker, id, d),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) newCancelExternalWorkflowStateMachine(attributes *commandpb.RequestCancelExternalWorkflowExecutionCommandAttributes, cancellationID string) *cancelExternalWorkflowCommandStateMachine {
|
||
|
d := createNewCommand(enumspb.COMMAND_TYPE_REQUEST_CANCEL_EXTERNAL_WORKFLOW_EXECUTION)
|
||
|
d.Attributes = &commandpb.Command_RequestCancelExternalWorkflowExecutionCommandAttributes{RequestCancelExternalWorkflowExecutionCommandAttributes: attributes}
|
||
|
return &cancelExternalWorkflowCommandStateMachine{
|
||
|
naiveCommandStateMachine: h.newNaiveCommandStateMachine(commandTypeCancellation, cancellationID, d),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) newSignalExternalWorkflowStateMachine(attributes *commandpb.SignalExternalWorkflowExecutionCommandAttributes, signalID string) *signalExternalWorkflowCommandStateMachine {
|
||
|
d := createNewCommand(enumspb.COMMAND_TYPE_SIGNAL_EXTERNAL_WORKFLOW_EXECUTION)
|
||
|
d.Attributes = &commandpb.Command_SignalExternalWorkflowExecutionCommandAttributes{SignalExternalWorkflowExecutionCommandAttributes: attributes}
|
||
|
return &signalExternalWorkflowCommandStateMachine{
|
||
|
naiveCommandStateMachine: h.newNaiveCommandStateMachine(commandTypeSignal, signalID, d),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) newUpsertSearchAttributesStateMachine(attributes *commandpb.UpsertWorkflowSearchAttributesCommandAttributes, upsertID string) *upsertSearchAttributesCommandStateMachine {
|
||
|
d := createNewCommand(enumspb.COMMAND_TYPE_UPSERT_WORKFLOW_SEARCH_ATTRIBUTES)
|
||
|
d.Attributes = &commandpb.Command_UpsertWorkflowSearchAttributesCommandAttributes{UpsertWorkflowSearchAttributesCommandAttributes: attributes}
|
||
|
return &upsertSearchAttributesCommandStateMachine{
|
||
|
naiveCommandStateMachine: h.newNaiveCommandStateMachine(commandTypeUpsertSearchAttributes, upsertID, d),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (d *commandStateMachineBase) getState() commandState {
|
||
|
return d.state
|
||
|
}
|
||
|
|
||
|
func (d *commandStateMachineBase) getID() commandID {
|
||
|
return d.id
|
||
|
}
|
||
|
|
||
|
func (d *commandStateMachineBase) isDone() bool {
|
||
|
return d.state == commandStateCompleted || d.state == commandStateCompletedAfterCancellationCommandSent
|
||
|
}
|
||
|
|
||
|
func (d *commandStateMachineBase) setData(data interface{}) {
|
||
|
d.data = data
|
||
|
}
|
||
|
|
||
|
func (d *commandStateMachineBase) getData() interface{} {
|
||
|
return d.data
|
||
|
}
|
||
|
|
||
|
func (d *commandStateMachineBase) moveState(newState commandState, event string) {
|
||
|
d.history = append(d.history, event)
|
||
|
d.state = newState
|
||
|
d.history = append(d.history, newState.String())
|
||
|
|
||
|
if newState == commandStateCompleted {
|
||
|
if elem, ok := d.helper.commands[d.getID()]; ok {
|
||
|
d.helper.orderedCommands.Remove(elem)
|
||
|
delete(d.helper.commands, d.getID())
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (d stateMachineIllegalStatePanic) String() string {
|
||
|
return d.message
|
||
|
}
|
||
|
|
||
|
func panicIllegalState(message string) {
|
||
|
panic(stateMachineIllegalStatePanic{message: message})
|
||
|
}
|
||
|
|
||
|
func (d *commandStateMachineBase) failStateTransition(event string) {
|
||
|
// this is when we detect illegal state transition, likely due to ill history sequence or nondeterministic workflow code
|
||
|
panicIllegalState(fmt.Sprintf("invalid state transition: attempt to %v, %v", event, d))
|
||
|
}
|
||
|
|
||
|
func (d *commandStateMachineBase) handleCommandSent() {
|
||
|
switch d.state {
|
||
|
case commandStateCreated:
|
||
|
d.moveState(commandStateCommandSent, eventCommandSent)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (d *commandStateMachineBase) cancel() {
|
||
|
switch d.state {
|
||
|
case commandStateCompleted, commandStateCompletedAfterCancellationCommandSent:
|
||
|
// No op. This is legit. People could cancel context after timer/activity is done.
|
||
|
case commandStateCreated:
|
||
|
d.moveState(commandStateCanceledBeforeSent, eventCancel)
|
||
|
case commandStateCommandSent:
|
||
|
d.moveState(commandStateCancellationCommandSent, eventCancel)
|
||
|
case commandStateInitiated:
|
||
|
if d.helper.workflowExecutionIsCancelling {
|
||
|
d.helper.commandsCancelledDuringWFCancellation++
|
||
|
}
|
||
|
d.moveState(commandStateCanceledAfterInitiated, eventCancel)
|
||
|
default:
|
||
|
d.failStateTransition(eventCancel)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (d *commandStateMachineBase) handleInitiatedEvent() {
|
||
|
switch d.state {
|
||
|
case commandStateCommandSent:
|
||
|
d.moveState(commandStateInitiated, eventInitiated)
|
||
|
case commandStateCanceledBeforeInitiated, commandStateCanceledBeforeSent, commandStateCancellationCommandSent:
|
||
|
d.moveState(commandStateCanceledAfterInitiated, eventInitiated)
|
||
|
default:
|
||
|
d.failStateTransition(eventInitiated)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (d *commandStateMachineBase) handleInitiationFailedEvent() {
|
||
|
switch d.state {
|
||
|
case commandStateInitiated, commandStateCommandSent, commandStateCanceledBeforeInitiated, commandStateCancellationCommandSent:
|
||
|
d.moveState(commandStateCompleted, eventInitiationFailed)
|
||
|
default:
|
||
|
d.failStateTransition(eventInitiationFailed)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (d *commandStateMachineBase) handleStartedEvent() {
|
||
|
d.history = append(d.history, eventStarted)
|
||
|
}
|
||
|
|
||
|
func (d *commandStateMachineBase) handleCompletionEvent() {
|
||
|
switch d.state {
|
||
|
case commandStateCanceledAfterInitiated, commandStateInitiated:
|
||
|
d.moveState(commandStateCompleted, eventCompletion)
|
||
|
case commandStateCancellationCommandSent:
|
||
|
d.moveState(commandStateCompletedAfterCancellationCommandSent, eventCompletion)
|
||
|
default:
|
||
|
d.failStateTransition(eventCompletion)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (d *commandStateMachineBase) handleCancelInitiatedEvent() {
|
||
|
d.history = append(d.history, eventCancelInitiated)
|
||
|
switch d.state {
|
||
|
case commandStateCancellationCommandSent, commandStateCanceledAfterInitiated:
|
||
|
// No state change
|
||
|
default:
|
||
|
d.failStateTransition(eventCancelInitiated)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (d *commandStateMachineBase) handleCancelFailedEvent() {
|
||
|
switch d.state {
|
||
|
case commandStateCompletedAfterCancellationCommandSent:
|
||
|
d.moveState(commandStateCompleted, eventCancelFailed)
|
||
|
default:
|
||
|
d.failStateTransition(eventCancelFailed)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (d *commandStateMachineBase) handleCanceledEvent() {
|
||
|
switch d.state {
|
||
|
case commandStateCancellationCommandSent, commandStateCanceledAfterInitiated, commandStateCancellationCommandAccepted:
|
||
|
d.moveState(commandStateCompleted, eventCanceled)
|
||
|
default:
|
||
|
d.failStateTransition(eventCanceled)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (d *commandStateMachineBase) String() string {
|
||
|
return fmt.Sprintf("%v, state=%v, isDone()=%v, history=%v",
|
||
|
d.id, d.state, d.isDone(), d.history)
|
||
|
}
|
||
|
|
||
|
func (d *activityCommandStateMachine) getCommand() *commandpb.Command {
|
||
|
switch d.state {
|
||
|
case commandStateCreated, commandStateCanceledBeforeSent:
|
||
|
command := createNewCommand(enumspb.COMMAND_TYPE_SCHEDULE_ACTIVITY_TASK)
|
||
|
command.Attributes = &commandpb.Command_ScheduleActivityTaskCommandAttributes{ScheduleActivityTaskCommandAttributes: d.attributes}
|
||
|
return command
|
||
|
default:
|
||
|
return nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (d *activityCommandStateMachine) handleCommandSent() {
|
||
|
switch d.state {
|
||
|
case commandStateCanceledAfterInitiated:
|
||
|
d.moveState(commandStateCancellationCommandSent, eventCommandSent)
|
||
|
default:
|
||
|
d.commandStateMachineBase.handleCommandSent()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (d *activityCommandStateMachine) handleCancelFailedEvent() {
|
||
|
// Request to cancel activity now results in either activity completion, failed, timedout, or canceled
|
||
|
// Request to cancel itself can never fail and invalid RequestCancelActivity commands results in the
|
||
|
// entire command being failed.
|
||
|
d.failStateTransition(eventCancelFailed)
|
||
|
}
|
||
|
|
||
|
func (d *activityCommandStateMachine) cancel() {
|
||
|
switch d.state {
|
||
|
case commandStateCreated, commandStateCommandSent, commandStateInitiated:
|
||
|
attribs := &commandpb.RequestCancelActivityTaskCommandAttributes{
|
||
|
ScheduledEventId: d.scheduleID,
|
||
|
}
|
||
|
cancelCmd := d.helper.newCancelActivityStateMachine(attribs)
|
||
|
d.helper.addCommand(cancelCmd)
|
||
|
// We must mark the event ID reset counter for when we performed this
|
||
|
// increment so a potential decrement can only decrement if it wasn't
|
||
|
// reset
|
||
|
cancelCmd.cancelledOnEventIDResetCounter = d.helper.nextCommandEventIDResetCounter
|
||
|
}
|
||
|
|
||
|
d.commandStateMachineBase.cancel()
|
||
|
}
|
||
|
|
||
|
func (d *timerCommandStateMachine) cancel() {
|
||
|
switch d.state {
|
||
|
case commandStateCreated, commandStateCommandSent, commandStateInitiated:
|
||
|
attribs := &commandpb.CancelTimerCommandAttributes{
|
||
|
TimerId: d.attributes.TimerId,
|
||
|
}
|
||
|
cancelCmd := d.helper.newCancelTimerCommandStateMachine(attribs)
|
||
|
d.helper.addCommand(cancelCmd)
|
||
|
// We must mark the event ID reset counter for when we performed this
|
||
|
// increment so a potential decrement can only decrement if it wasn't
|
||
|
// reset
|
||
|
cancelCmd.cancelledOnEventIDResetCounter = d.helper.nextCommandEventIDResetCounter
|
||
|
}
|
||
|
|
||
|
d.commandStateMachineBase.cancel()
|
||
|
}
|
||
|
|
||
|
func (d *timerCommandStateMachine) isDone() bool {
|
||
|
return d.state == commandStateCompleted
|
||
|
}
|
||
|
|
||
|
func (d *timerCommandStateMachine) handleCommandSent() {
|
||
|
switch d.state {
|
||
|
case commandStateCanceledAfterInitiated:
|
||
|
d.moveState(commandStateCancellationCommandSent, eventCommandSent)
|
||
|
default:
|
||
|
d.commandStateMachineBase.handleCommandSent()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (d *cancelActivityStateMachine) getCommand() *commandpb.Command {
|
||
|
switch d.state {
|
||
|
case commandStateCreated:
|
||
|
command := createNewCommand(enumspb.COMMAND_TYPE_REQUEST_CANCEL_ACTIVITY_TASK)
|
||
|
command.Attributes = &commandpb.Command_RequestCancelActivityTaskCommandAttributes{RequestCancelActivityTaskCommandAttributes: d.attributes}
|
||
|
return command
|
||
|
default:
|
||
|
return nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (d *timerCommandStateMachine) getCommand() *commandpb.Command {
|
||
|
switch d.state {
|
||
|
case commandStateCreated, commandStateCanceledBeforeSent:
|
||
|
command := createNewCommand(enumspb.COMMAND_TYPE_START_TIMER)
|
||
|
command.Attributes = &commandpb.Command_StartTimerCommandAttributes{StartTimerCommandAttributes: d.attributes}
|
||
|
return command
|
||
|
default:
|
||
|
return nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (d *cancelTimerCommandStateMachine) getCommand() *commandpb.Command {
|
||
|
switch d.state {
|
||
|
case commandStateCreated:
|
||
|
command := createNewCommand(enumspb.COMMAND_TYPE_CANCEL_TIMER)
|
||
|
command.Attributes = &commandpb.Command_CancelTimerCommandAttributes{CancelTimerCommandAttributes: d.attributes}
|
||
|
return command
|
||
|
default:
|
||
|
return nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (d *childWorkflowCommandStateMachine) getCommand() *commandpb.Command {
|
||
|
switch d.state {
|
||
|
case commandStateCreated:
|
||
|
command := createNewCommand(enumspb.COMMAND_TYPE_START_CHILD_WORKFLOW_EXECUTION)
|
||
|
command.Attributes = &commandpb.Command_StartChildWorkflowExecutionCommandAttributes{StartChildWorkflowExecutionCommandAttributes: d.attributes}
|
||
|
return command
|
||
|
case commandStateCanceledAfterStarted:
|
||
|
command := createNewCommand(enumspb.COMMAND_TYPE_REQUEST_CANCEL_EXTERNAL_WORKFLOW_EXECUTION)
|
||
|
command.Attributes = &commandpb.Command_RequestCancelExternalWorkflowExecutionCommandAttributes{RequestCancelExternalWorkflowExecutionCommandAttributes: &commandpb.RequestCancelExternalWorkflowExecutionCommandAttributes{
|
||
|
Namespace: d.attributes.Namespace,
|
||
|
WorkflowId: d.attributes.WorkflowId,
|
||
|
ChildWorkflowOnly: true,
|
||
|
}}
|
||
|
return command
|
||
|
default:
|
||
|
return nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (d *childWorkflowCommandStateMachine) handleCommandSent() {
|
||
|
switch d.state {
|
||
|
case commandStateCanceledAfterStarted:
|
||
|
d.moveState(commandStateCancellationCommandSent, eventCommandSent)
|
||
|
default:
|
||
|
d.commandStateMachineBase.handleCommandSent()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (d *childWorkflowCommandStateMachine) handleStartedEvent() {
|
||
|
switch d.state {
|
||
|
case commandStateInitiated:
|
||
|
d.moveState(commandStateStarted, eventStarted)
|
||
|
case commandStateCanceledAfterInitiated:
|
||
|
d.moveState(commandStateCanceledAfterStarted, eventStarted)
|
||
|
default:
|
||
|
d.commandStateMachineBase.handleStartedEvent()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (d *childWorkflowCommandStateMachine) handleInitiatedEvent() {
|
||
|
switch d.state {
|
||
|
case commandStateCancellationCommandSent:
|
||
|
d.failStateTransition(eventInitiated)
|
||
|
default:
|
||
|
d.commandStateMachineBase.handleInitiatedEvent()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (d *childWorkflowCommandStateMachine) handleCancelFailedEvent() {
|
||
|
switch d.state {
|
||
|
case commandStateCancellationCommandSent:
|
||
|
d.moveState(commandStateStarted, eventCancelFailed)
|
||
|
default:
|
||
|
d.commandStateMachineBase.handleCancelFailedEvent()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (d *childWorkflowCommandStateMachine) cancel() {
|
||
|
switch d.state {
|
||
|
case commandStateStarted:
|
||
|
if d.helper.workflowExecutionIsCancelling {
|
||
|
d.helper.commandsCancelledDuringWFCancellation++
|
||
|
}
|
||
|
d.moveState(commandStateCanceledAfterStarted, eventCancel)
|
||
|
// A child workflow may be canceled _after_ something like an activity start
|
||
|
// happens inside a simulated goroutine. However, since the state of the
|
||
|
// entire child workflow is recorded based on when it started not when it
|
||
|
// was canceled, we have to move it to the end once canceled to keep the
|
||
|
// expected commands in order of when they actually occurred.
|
||
|
d.helper.moveCommandToBack(d)
|
||
|
d.helper.incrementNextCommandEventID()
|
||
|
default:
|
||
|
d.commandStateMachineBase.cancel()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (d *childWorkflowCommandStateMachine) handleCanceledEvent() {
|
||
|
switch d.state {
|
||
|
case commandStateStarted:
|
||
|
d.moveState(commandStateCompleted, eventCanceled)
|
||
|
case commandStateCancellationCommandSent:
|
||
|
// We've sent the command but haven't seen the server accept the cancellation. We must ensure this command hangs
|
||
|
// around, because it is possible for the child workflow to be canceled before we've seen the event.
|
||
|
d.moveState(commandStateCompletedAfterCancellationCommandSent, eventCanceled)
|
||
|
default:
|
||
|
d.commandStateMachineBase.handleCanceledEvent()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (d *childWorkflowCommandStateMachine) handleCompletionEvent() {
|
||
|
switch d.state {
|
||
|
case commandStateStarted, commandStateCanceledAfterStarted,
|
||
|
commandStateCompletedAfterCancellationCommandSent, commandStateCancellationCommandAccepted:
|
||
|
d.moveState(commandStateCompleted, eventCompletion)
|
||
|
default:
|
||
|
d.commandStateMachineBase.handleCompletionEvent()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (d *childWorkflowCommandStateMachine) handleExternalWorkflowExecutionCancelRequested() {
|
||
|
if d.getState() == commandStateCompletedAfterCancellationCommandSent {
|
||
|
// Now we're really done.
|
||
|
d.handleCompletionEvent()
|
||
|
} else {
|
||
|
// We should be in the cancellation command sent stage - new state to indicate we have seen the cancel accepted
|
||
|
d.moveState(commandStateCancellationCommandAccepted, eventExternalWorkflowExecutionCancelRequested)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (d *naiveCommandStateMachine) getCommand() *commandpb.Command {
|
||
|
switch d.state {
|
||
|
case commandStateCreated:
|
||
|
return d.command
|
||
|
default:
|
||
|
return nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (d *naiveCommandStateMachine) cancel() {
|
||
|
panic("unsupported operation")
|
||
|
}
|
||
|
|
||
|
func (d *naiveCommandStateMachine) handleCompletionEvent() {
|
||
|
panic("unsupported operation")
|
||
|
}
|
||
|
|
||
|
func (d *naiveCommandStateMachine) handleInitiatedEvent() {
|
||
|
panic("unsupported operation")
|
||
|
}
|
||
|
|
||
|
func (d *naiveCommandStateMachine) handleInitiationFailedEvent() {
|
||
|
panic("unsupported operation")
|
||
|
}
|
||
|
|
||
|
func (d *naiveCommandStateMachine) handleStartedEvent() {
|
||
|
panic("unsupported operation")
|
||
|
}
|
||
|
|
||
|
func (d *naiveCommandStateMachine) handleCanceledEvent() {
|
||
|
panic("unsupported operation")
|
||
|
}
|
||
|
|
||
|
func (d *naiveCommandStateMachine) handleCancelFailedEvent() {
|
||
|
panic("unsupported operation")
|
||
|
}
|
||
|
|
||
|
func (d *naiveCommandStateMachine) handleCancelInitiatedEvent() {
|
||
|
panic("unsupported operation")
|
||
|
}
|
||
|
|
||
|
func (d *cancelExternalWorkflowCommandStateMachine) handleInitiatedEvent() {
|
||
|
switch d.state {
|
||
|
case commandStateCommandSent:
|
||
|
d.moveState(commandStateInitiated, eventInitiated)
|
||
|
default:
|
||
|
d.failStateTransition(eventInitiated)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (d *cancelExternalWorkflowCommandStateMachine) handleCompletionEvent() {
|
||
|
switch d.state {
|
||
|
case commandStateInitiated:
|
||
|
d.moveState(commandStateCompleted, eventCompletion)
|
||
|
default:
|
||
|
d.failStateTransition(eventCompletion)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (d *signalExternalWorkflowCommandStateMachine) handleInitiatedEvent() {
|
||
|
switch d.state {
|
||
|
case commandStateCommandSent:
|
||
|
d.moveState(commandStateInitiated, eventInitiated)
|
||
|
default:
|
||
|
d.failStateTransition(eventInitiated)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (d *signalExternalWorkflowCommandStateMachine) handleCompletionEvent() {
|
||
|
switch d.state {
|
||
|
case commandStateInitiated:
|
||
|
d.moveState(commandStateCompleted, eventCompletion)
|
||
|
default:
|
||
|
d.failStateTransition(eventCompletion)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (d *markerCommandStateMachine) handleCommandSent() {
|
||
|
// Marker command state machine is considered as completed once command is sent.
|
||
|
// For SideEffect/Version markers, when the history event is applied, there is no marker command state machine yet
|
||
|
// because we preload those marker events.
|
||
|
// For local activity, when we apply the history event, we use it to create the marker state machine, there is no
|
||
|
// other event to drive it to completed state.
|
||
|
switch d.state {
|
||
|
case commandStateCreated:
|
||
|
d.moveState(commandStateCompleted, eventCommandSent)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (d *upsertSearchAttributesCommandStateMachine) handleCommandSent() {
|
||
|
// This command is considered as completed once command is sent.
|
||
|
switch d.state {
|
||
|
case commandStateCreated:
|
||
|
d.moveState(commandStateCompleted, eventCommandSent)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func newCommandsHelper() *commandsHelper {
|
||
|
return &commandsHelper{
|
||
|
orderedCommands: list.New(),
|
||
|
commands: make(map[commandID]*list.Element),
|
||
|
|
||
|
scheduledEventIDToActivityID: make(map[int64]string),
|
||
|
scheduledEventIDToCancellationID: make(map[int64]string),
|
||
|
scheduledEventIDToSignalID: make(map[int64]string),
|
||
|
versionMarkerLookup: make(map[int64]string),
|
||
|
commandsCancelledDuringWFCancellation: 0,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) incrementNextCommandEventID() {
|
||
|
h.nextCommandEventID++
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) setCurrentWorkflowTaskStartedEventID(workflowTaskStartedEventID int64) {
|
||
|
// Server always processes the commands in the same order it is generated by client and each command results in
|
||
|
// corresponding history event after processing. So we can use workflow task started event id + 2 as the offset as
|
||
|
// workflow task completed event is always the first event in the workflow task followed by events generated from
|
||
|
// commands. This allows client sdk to deterministically predict history event ids generated by processing of the
|
||
|
// command. We must also add the number of cancel commands that were spawned during cancellation of the workflow
|
||
|
// execution as those canceled command events will show up *after* the workflow task completed event.
|
||
|
h.nextCommandEventID = workflowTaskStartedEventID + 2 + h.commandsCancelledDuringWFCancellation
|
||
|
h.commandsCancelledDuringWFCancellation = 0
|
||
|
// We must change the counter here so that others who mutate
|
||
|
// commandsCancelledDuringWFCancellation know it has since been reset
|
||
|
h.nextCommandEventIDResetCounter++
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) getNextID() int64 {
|
||
|
// First check if we have a GetVersion marker in the lookup map
|
||
|
h.incrementNextCommandEventIDIfVersionMarker()
|
||
|
if h.nextCommandEventID == 0 {
|
||
|
panic("Attempt to generate a command before processing WorkflowTaskStarted event")
|
||
|
}
|
||
|
return h.nextCommandEventID
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) incrementNextCommandEventIDIfVersionMarker() {
|
||
|
_, ok := h.versionMarkerLookup[h.nextCommandEventID]
|
||
|
for ok {
|
||
|
// Remove the marker from the lookup map and increment nextCommandEventID by 2 because call to GetVersion
|
||
|
// results in 2 events in the history. One is GetVersion marker event for changeID and change version, other
|
||
|
// is UpsertSearchableAttributes to keep track of executions using particular version of code.
|
||
|
delete(h.versionMarkerLookup, h.nextCommandEventID)
|
||
|
h.incrementNextCommandEventID()
|
||
|
h.incrementNextCommandEventID()
|
||
|
_, ok = h.versionMarkerLookup[h.nextCommandEventID]
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) getCommand(id commandID) commandStateMachine {
|
||
|
command, ok := h.commands[id]
|
||
|
if !ok {
|
||
|
panicMsg := fmt.Sprintf("unknown command %v, possible causes are nondeterministic workflow definition code"+
|
||
|
" or incompatible change in the workflow definition", id)
|
||
|
panicIllegalState(panicMsg)
|
||
|
}
|
||
|
return command.Value.(commandStateMachine)
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) addCommand(command commandStateMachine) {
|
||
|
if _, ok := h.commands[command.getID()]; ok {
|
||
|
panicMsg := fmt.Sprintf("adding duplicate command %v", command)
|
||
|
panicIllegalState(panicMsg)
|
||
|
}
|
||
|
element := h.orderedCommands.PushBack(command)
|
||
|
h.commands[command.getID()] = element
|
||
|
|
||
|
// Every time new command is added increment the counter used for generating ID
|
||
|
h.incrementNextCommandEventIDIfVersionMarker()
|
||
|
h.incrementNextCommandEventID()
|
||
|
}
|
||
|
|
||
|
// This really should not exist, but is unavoidable without totally redesigning the Go SDK to avoid
|
||
|
// doing event number counting. EX: Because a workflow execution cancel requested event calls a callback
|
||
|
// on timers that immediately cancels them, we will queue up a cancel timer command even though that timer firing
|
||
|
// might be in the same workflow task. In practice this only seems to happen during unhandled command events.
|
||
|
func (h *commandsHelper) removeCancelOfResolvedCommand(commandID commandID) {
|
||
|
// Ensure this isn't misused for non-cancel commands
|
||
|
if commandID.commandType != commandTypeCancelTimer && commandID.commandType != commandTypeRequestCancelActivityTask {
|
||
|
panic("removeCancelOfResolvedCommand should only be called for cancel timer / activity")
|
||
|
}
|
||
|
orderedCmdEl, ok := h.commands[commandID]
|
||
|
if ok {
|
||
|
delete(h.commands, commandID)
|
||
|
command := h.orderedCommands.Remove(orderedCmdEl)
|
||
|
// Sometimes commandsCancelledDuringWFCancellation was incremented before
|
||
|
// it was reset and sometimes not. We use the reset counter to see if we're
|
||
|
// still on the same iteration where we may have incremented it before.
|
||
|
switch command := command.(type) {
|
||
|
case *cancelActivityStateMachine:
|
||
|
if command.cancelledOnEventIDResetCounter == h.nextCommandEventIDResetCounter {
|
||
|
h.commandsCancelledDuringWFCancellation--
|
||
|
}
|
||
|
case *cancelTimerCommandStateMachine:
|
||
|
if command.cancelledOnEventIDResetCounter == h.nextCommandEventIDResetCounter {
|
||
|
h.commandsCancelledDuringWFCancellation--
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) moveCommandToBack(command commandStateMachine) {
|
||
|
elem := h.commands[command.getID()]
|
||
|
if elem == nil {
|
||
|
panicIllegalState(fmt.Sprintf("moving command not present %v", command))
|
||
|
}
|
||
|
h.orderedCommands.Remove(elem)
|
||
|
h.commands[command.getID()] = h.orderedCommands.PushBack(command)
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) scheduleActivityTask(
|
||
|
scheduleID int64,
|
||
|
attributes *commandpb.ScheduleActivityTaskCommandAttributes,
|
||
|
) commandStateMachine {
|
||
|
h.scheduledEventIDToActivityID[scheduleID] = attributes.GetActivityId()
|
||
|
command := h.newActivityCommandStateMachine(scheduleID, attributes)
|
||
|
h.addCommand(command)
|
||
|
return command
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) requestCancelActivityTask(activityID string) commandStateMachine {
|
||
|
id := makeCommandID(commandTypeActivity, activityID)
|
||
|
command := h.getCommand(id)
|
||
|
command.cancel()
|
||
|
return command
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) handleActivityTaskClosed(activityID string, scheduledEventID int64) commandStateMachine {
|
||
|
command := h.getCommand(makeCommandID(commandTypeActivity, activityID))
|
||
|
// If, for whatever reason, we were going to send an activity cancel request, don't do that anymore
|
||
|
// since we already know the activity is resolved.
|
||
|
possibleCancelID := makeCommandID(commandTypeRequestCancelActivityTask, activityID)
|
||
|
h.removeCancelOfResolvedCommand(possibleCancelID)
|
||
|
command.handleCompletionEvent()
|
||
|
delete(h.scheduledEventIDToActivityID, scheduledEventID)
|
||
|
return command
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) handleActivityTaskScheduled(activityID string, scheduledEventID int64) {
|
||
|
if _, ok := h.scheduledEventIDToActivityID[scheduledEventID]; !ok {
|
||
|
panicMsg := fmt.Sprintf("lookup failed for scheduledEventID to activityID: scheduleEventID: %v, activityID: %v",
|
||
|
scheduledEventID, activityID)
|
||
|
panicIllegalState(panicMsg)
|
||
|
}
|
||
|
|
||
|
command := h.getCommand(makeCommandID(commandTypeActivity, activityID))
|
||
|
command.handleInitiatedEvent()
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) handleActivityTaskCancelRequested(scheduledEventID int64) {
|
||
|
activityID, ok := h.scheduledEventIDToActivityID[scheduledEventID]
|
||
|
if !ok {
|
||
|
panicIllegalState(fmt.Sprintf("unable to find activityID for the scheduledEventID: %v", scheduledEventID))
|
||
|
}
|
||
|
command := h.getCommand(makeCommandID(commandTypeActivity, activityID))
|
||
|
command.handleCancelInitiatedEvent()
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) handleActivityTaskCanceled(activityID string, scheduledEventID int64) commandStateMachine {
|
||
|
command := h.getCommand(makeCommandID(commandTypeActivity, activityID))
|
||
|
command.handleCanceledEvent()
|
||
|
delete(h.scheduledEventIDToActivityID, scheduledEventID)
|
||
|
return command
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) getActivityAndScheduledEventIDs(event *historypb.HistoryEvent) (string, int64) {
|
||
|
var scheduledEventID int64 = -1
|
||
|
switch event.GetEventType() {
|
||
|
case enumspb.EVENT_TYPE_ACTIVITY_TASK_CANCELED:
|
||
|
scheduledEventID = event.GetActivityTaskCanceledEventAttributes().GetScheduledEventId()
|
||
|
case enumspb.EVENT_TYPE_ACTIVITY_TASK_COMPLETED:
|
||
|
scheduledEventID = event.GetActivityTaskCompletedEventAttributes().GetScheduledEventId()
|
||
|
case enumspb.EVENT_TYPE_ACTIVITY_TASK_FAILED:
|
||
|
scheduledEventID = event.GetActivityTaskFailedEventAttributes().GetScheduledEventId()
|
||
|
case enumspb.EVENT_TYPE_ACTIVITY_TASK_TIMED_OUT:
|
||
|
scheduledEventID = event.GetActivityTaskTimedOutEventAttributes().GetScheduledEventId()
|
||
|
default:
|
||
|
panicIllegalState(fmt.Sprintf("unexpected event type: %v", event.GetEventType()))
|
||
|
}
|
||
|
|
||
|
activityID, ok := h.scheduledEventIDToActivityID[scheduledEventID]
|
||
|
if !ok {
|
||
|
panicIllegalState(fmt.Sprintf("unable to find activityID for the event: %v", util.HistoryEventToString(event)))
|
||
|
}
|
||
|
return activityID, scheduledEventID
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) recordVersionMarker(changeID string, version Version, dc converter.DataConverter) commandStateMachine {
|
||
|
markerID := fmt.Sprintf("%v_%v", versionMarkerName, changeID)
|
||
|
|
||
|
changeIDPayload, err := dc.ToPayloads(changeID)
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
|
||
|
versionPayload, err := dc.ToPayloads(version)
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
|
||
|
recordMarker := &commandpb.RecordMarkerCommandAttributes{
|
||
|
MarkerName: versionMarkerName,
|
||
|
Details: map[string]*commonpb.Payloads{
|
||
|
versionMarkerChangeIDName: changeIDPayload,
|
||
|
versionMarkerDataName: versionPayload,
|
||
|
},
|
||
|
}
|
||
|
|
||
|
command := h.newMarkerCommandStateMachine(markerID, recordMarker)
|
||
|
h.addCommand(command)
|
||
|
return command
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) handleVersionMarker(eventID int64, changeID string) {
|
||
|
if _, ok := h.versionMarkerLookup[eventID]; ok {
|
||
|
panicMsg := fmt.Sprintf("marker event already exists for eventID in lookup: eventID: %v, changeID: %v",
|
||
|
eventID, changeID)
|
||
|
panicIllegalState(panicMsg)
|
||
|
}
|
||
|
|
||
|
// During processing of a workflow task we reorder all GetVersion markers and process them first.
|
||
|
// Keep track of all GetVersion marker events during the processing of workflow task so we can
|
||
|
// generate correct eventIDs for other events during replay.
|
||
|
h.versionMarkerLookup[eventID] = changeID
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) recordSideEffectMarker(sideEffectID int64, data *commonpb.Payloads, dc converter.DataConverter) commandStateMachine {
|
||
|
markerID := fmt.Sprintf("%v_%v", sideEffectMarkerName, sideEffectID)
|
||
|
sideEffectIDPayload, err := dc.ToPayloads(sideEffectID)
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
|
||
|
attributes := &commandpb.RecordMarkerCommandAttributes{
|
||
|
MarkerName: sideEffectMarkerName,
|
||
|
Details: map[string]*commonpb.Payloads{
|
||
|
sideEffectMarkerIDName: sideEffectIDPayload,
|
||
|
sideEffectMarkerDataName: data,
|
||
|
},
|
||
|
}
|
||
|
command := h.newMarkerCommandStateMachine(markerID, attributes)
|
||
|
h.addCommand(command)
|
||
|
return command
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) recordLocalActivityMarker(activityID string, details map[string]*commonpb.Payloads, failure *failurepb.Failure) commandStateMachine {
|
||
|
markerID := fmt.Sprintf("%v_%v", localActivityMarkerName, activityID)
|
||
|
attributes := &commandpb.RecordMarkerCommandAttributes{
|
||
|
MarkerName: localActivityMarkerName,
|
||
|
Failure: failure,
|
||
|
Details: details,
|
||
|
}
|
||
|
command := h.newMarkerCommandStateMachine(markerID, attributes)
|
||
|
// LocalActivity marker is added only when it completes and schedule logic never relies on GenerateSequence to
|
||
|
// create a unique activity id like in the case of ExecuteActivity. This causes the problem as we only perform
|
||
|
// the check to increment counter to account for GetVersion special handling as part of it. This will result
|
||
|
// in wrong IDs to be generated if there is GetVersion call before local activities. Explicitly calling getNextID
|
||
|
// to correctly incrementing counter before adding the command.
|
||
|
h.getNextID()
|
||
|
h.addCommand(command)
|
||
|
return command
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) recordMutableSideEffectMarker(mutableSideEffectID string, data *commonpb.Payloads, dc converter.DataConverter) commandStateMachine {
|
||
|
markerID := fmt.Sprintf("%v_%v", mutableSideEffectMarkerName, mutableSideEffectID)
|
||
|
|
||
|
mutableSideEffectIDPayload, err := dc.ToPayloads(mutableSideEffectID)
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
|
||
|
attributes := &commandpb.RecordMarkerCommandAttributes{
|
||
|
MarkerName: mutableSideEffectMarkerName,
|
||
|
Details: map[string]*commonpb.Payloads{
|
||
|
sideEffectMarkerIDName: mutableSideEffectIDPayload,
|
||
|
sideEffectMarkerDataName: data,
|
||
|
},
|
||
|
}
|
||
|
command := h.newMarkerCommandStateMachine(markerID, attributes)
|
||
|
h.addCommand(command)
|
||
|
return command
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) startChildWorkflowExecution(attributes *commandpb.StartChildWorkflowExecutionCommandAttributes) commandStateMachine {
|
||
|
command := h.newChildWorkflowCommandStateMachine(attributes)
|
||
|
h.addCommand(command)
|
||
|
return command
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) handleStartChildWorkflowExecutionInitiated(workflowID string) {
|
||
|
command := h.getCommand(makeCommandID(commandTypeChildWorkflow, workflowID))
|
||
|
command.handleInitiatedEvent()
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) handleStartChildWorkflowExecutionFailed(workflowID string) commandStateMachine {
|
||
|
command := h.getCommand(makeCommandID(commandTypeChildWorkflow, workflowID))
|
||
|
command.handleInitiationFailedEvent()
|
||
|
return command
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) requestCancelExternalWorkflowExecution(namespace, workflowID, runID string, cancellationID string, childWorkflowOnly bool) commandStateMachine {
|
||
|
if childWorkflowOnly {
|
||
|
// For cancellation of child workflow only, we do not use cancellation ID
|
||
|
// since the child workflow cancellation go through the existing child workflow
|
||
|
// state machine, and we use workflow ID as identifier
|
||
|
// we also do not use run ID, since child workflow can do continue-as-new
|
||
|
// which will have different run ID
|
||
|
// there will be server side validation that target workflow is child workflow
|
||
|
|
||
|
// sanity check that cancellation ID is not set
|
||
|
if len(cancellationID) != 0 {
|
||
|
panic("cancellation on child workflow should not use cancellation ID")
|
||
|
}
|
||
|
// sanity check that run ID is not set
|
||
|
if len(runID) != 0 {
|
||
|
panic("cancellation on child workflow should not use run ID")
|
||
|
}
|
||
|
// targeting child workflow
|
||
|
command := h.getCommand(makeCommandID(commandTypeChildWorkflow, workflowID))
|
||
|
command.cancel()
|
||
|
return command
|
||
|
}
|
||
|
|
||
|
// For cancellation of external workflow, we have to use cancellation ID
|
||
|
// to identify different cancellation request (command) / response (history event)
|
||
|
// client can also use this code path to cancel its own child workflow, however, there will
|
||
|
// be no server side validation that target workflow is the child
|
||
|
|
||
|
// sanity check that cancellation ID is set
|
||
|
if len(cancellationID) == 0 {
|
||
|
panic("cancellation on external workflow should use cancellation ID")
|
||
|
}
|
||
|
attributes := &commandpb.RequestCancelExternalWorkflowExecutionCommandAttributes{
|
||
|
Namespace: namespace,
|
||
|
WorkflowId: workflowID,
|
||
|
RunId: runID,
|
||
|
Control: cancellationID,
|
||
|
ChildWorkflowOnly: false,
|
||
|
}
|
||
|
command := h.newCancelExternalWorkflowStateMachine(attributes, cancellationID)
|
||
|
h.addCommand(command)
|
||
|
|
||
|
return command
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) handleRequestCancelExternalWorkflowExecutionInitiated(initiatedeventID int64, workflowID, cancellationID string) {
|
||
|
if h.isCancelExternalWorkflowEventForChildWorkflow(cancellationID) {
|
||
|
// this is cancellation for child workflow only
|
||
|
command := h.getCommand(makeCommandID(commandTypeChildWorkflow, workflowID))
|
||
|
command.handleCancelInitiatedEvent()
|
||
|
} else {
|
||
|
// this is cancellation for external workflow
|
||
|
h.scheduledEventIDToCancellationID[initiatedeventID] = cancellationID
|
||
|
command := h.getCommand(makeCommandID(commandTypeCancellation, cancellationID))
|
||
|
command.handleInitiatedEvent()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) handleExternalWorkflowExecutionCancelRequested(initiatedeventID int64, workflowID string) (bool, commandStateMachine) {
|
||
|
var command commandStateMachine
|
||
|
cancellationID, isExternal := h.scheduledEventIDToCancellationID[initiatedeventID]
|
||
|
if !isExternal {
|
||
|
command = h.getCommand(makeCommandID(commandTypeChildWorkflow, workflowID))
|
||
|
asChildWfCmd := command.(*childWorkflowCommandStateMachine)
|
||
|
asChildWfCmd.handleExternalWorkflowExecutionCancelRequested()
|
||
|
} else {
|
||
|
// this is cancellation for external workflow
|
||
|
command = h.getCommand(makeCommandID(commandTypeCancellation, cancellationID))
|
||
|
command.handleCompletionEvent()
|
||
|
}
|
||
|
return isExternal, command
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) handleRequestCancelExternalWorkflowExecutionFailed(initiatedeventID int64, workflowID string) (bool, commandStateMachine) {
|
||
|
var command commandStateMachine
|
||
|
cancellationID, isExternal := h.scheduledEventIDToCancellationID[initiatedeventID]
|
||
|
if !isExternal {
|
||
|
// this is cancellation for child workflow only
|
||
|
command = h.getCommand(makeCommandID(commandTypeChildWorkflow, workflowID))
|
||
|
command.handleCancelFailedEvent()
|
||
|
} else {
|
||
|
// this is cancellation for external workflow
|
||
|
command = h.getCommand(makeCommandID(commandTypeCancellation, cancellationID))
|
||
|
command.handleCompletionEvent()
|
||
|
}
|
||
|
return isExternal, command
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) signalExternalWorkflowExecution(
|
||
|
namespace string,
|
||
|
workflowID string,
|
||
|
runID string,
|
||
|
signalName string,
|
||
|
input *commonpb.Payloads,
|
||
|
header *commonpb.Header,
|
||
|
signalID string,
|
||
|
childWorkflowOnly bool,
|
||
|
) commandStateMachine {
|
||
|
attributes := &commandpb.SignalExternalWorkflowExecutionCommandAttributes{
|
||
|
Namespace: namespace,
|
||
|
Execution: &commonpb.WorkflowExecution{
|
||
|
WorkflowId: workflowID,
|
||
|
RunId: runID,
|
||
|
},
|
||
|
SignalName: signalName,
|
||
|
Input: input,
|
||
|
Control: signalID,
|
||
|
ChildWorkflowOnly: childWorkflowOnly,
|
||
|
Header: header,
|
||
|
}
|
||
|
command := h.newSignalExternalWorkflowStateMachine(attributes, signalID)
|
||
|
h.addCommand(command)
|
||
|
return command
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) upsertSearchAttributes(upsertID string, searchAttr *commonpb.SearchAttributes) commandStateMachine {
|
||
|
attributes := &commandpb.UpsertWorkflowSearchAttributesCommandAttributes{
|
||
|
SearchAttributes: searchAttr,
|
||
|
}
|
||
|
command := h.newUpsertSearchAttributesStateMachine(attributes, upsertID)
|
||
|
h.addCommand(command)
|
||
|
return command
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) handleSignalExternalWorkflowExecutionInitiated(initiatedEventID int64, signalID string) {
|
||
|
h.scheduledEventIDToSignalID[initiatedEventID] = signalID
|
||
|
command := h.getCommand(makeCommandID(commandTypeSignal, signalID))
|
||
|
command.handleInitiatedEvent()
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) handleSignalExternalWorkflowExecutionCompleted(initiatedEventID int64) commandStateMachine {
|
||
|
command := h.getCommand(makeCommandID(commandTypeSignal, h.getSignalID(initiatedEventID)))
|
||
|
command.handleCompletionEvent()
|
||
|
return command
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) handleSignalExternalWorkflowExecutionFailed(initiatedEventID int64) commandStateMachine {
|
||
|
command := h.getCommand(makeCommandID(commandTypeSignal, h.getSignalID(initiatedEventID)))
|
||
|
command.handleCompletionEvent()
|
||
|
return command
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) getSignalID(initiatedEventID int64) string {
|
||
|
signalID, ok := h.scheduledEventIDToSignalID[initiatedEventID]
|
||
|
if !ok {
|
||
|
panic(fmt.Sprintf("unable to find signalID for initiatedEventID: %v", initiatedEventID))
|
||
|
}
|
||
|
return signalID
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) startTimer(attributes *commandpb.StartTimerCommandAttributes) commandStateMachine {
|
||
|
command := h.newTimerCommandStateMachine(attributes)
|
||
|
h.addCommand(command)
|
||
|
return command
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) cancelTimer(timerID TimerID) commandStateMachine {
|
||
|
command := h.getCommand(makeCommandID(commandTypeTimer, timerID.id))
|
||
|
command.cancel()
|
||
|
|
||
|
return command
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) handleTimerClosed(timerID string) commandStateMachine {
|
||
|
command := h.getCommand(makeCommandID(commandTypeTimer, timerID))
|
||
|
// If, for whatever reason, we were going to send a timer cancel command, don't do that anymore
|
||
|
// since we already know the timer is resolved.
|
||
|
possibleCancelID := makeCommandID(commandTypeCancelTimer, timerID)
|
||
|
h.removeCancelOfResolvedCommand(possibleCancelID)
|
||
|
command.handleCompletionEvent()
|
||
|
return command
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) handleTimerStarted(timerID string) {
|
||
|
command := h.getCommand(makeCommandID(commandTypeTimer, timerID))
|
||
|
command.handleInitiatedEvent()
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) handleTimerCanceled(timerID string) {
|
||
|
command := h.getCommand(makeCommandID(commandTypeTimer, timerID))
|
||
|
command.handleCanceledEvent()
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) handleChildWorkflowExecutionStarted(workflowID string) commandStateMachine {
|
||
|
command := h.getCommand(makeCommandID(commandTypeChildWorkflow, workflowID))
|
||
|
command.handleStartedEvent()
|
||
|
return command
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) handleChildWorkflowExecutionClosed(workflowID string) commandStateMachine {
|
||
|
command := h.getCommand(makeCommandID(commandTypeChildWorkflow, workflowID))
|
||
|
command.handleCompletionEvent()
|
||
|
return command
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) handleChildWorkflowExecutionCanceled(workflowID string) commandStateMachine {
|
||
|
command := h.getCommand(makeCommandID(commandTypeChildWorkflow, workflowID))
|
||
|
command.handleCanceledEvent()
|
||
|
return command
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) getCommands(markAsSent bool) []*commandpb.Command {
|
||
|
var result []*commandpb.Command
|
||
|
for curr := h.orderedCommands.Front(); curr != nil; {
|
||
|
next := curr.Next() // get next item here as we might need to remove curr in the loop
|
||
|
d := curr.Value.(commandStateMachine)
|
||
|
command := d.getCommand()
|
||
|
if command != nil {
|
||
|
result = append(result, command)
|
||
|
}
|
||
|
|
||
|
if markAsSent {
|
||
|
d.handleCommandSent()
|
||
|
}
|
||
|
|
||
|
// remove completed command state machines
|
||
|
if d.getState() == commandStateCompleted {
|
||
|
h.orderedCommands.Remove(curr)
|
||
|
delete(h.commands, d.getID())
|
||
|
}
|
||
|
|
||
|
curr = next
|
||
|
}
|
||
|
|
||
|
return result
|
||
|
}
|
||
|
|
||
|
func (h *commandsHelper) isCancelExternalWorkflowEventForChildWorkflow(cancellationID string) bool {
|
||
|
// the cancellationID, i.e. Control in RequestCancelExternalWorkflowExecutionInitiatedEventAttributes
|
||
|
// will be empty if the event is for child workflow.
|
||
|
// for cancellation external workflow, Control in RequestCancelExternalWorkflowExecutionInitiatedEventAttributes
|
||
|
// will have a client generated sequence ID
|
||
|
return len(cancellationID) == 0
|
||
|
}
|