@@ -34,6 +34,7 @@ require ( | |||
github.com/agiledragon/gomonkey v2.0.2+incompatible | |||
github.com/agiledragon/gomonkey/v2 v2.9.0 | |||
github.com/mattn/go-sqlite3 v1.14.19 | |||
golang.org/x/sync v0.6.0 | |||
google.golang.org/protobuf v1.30.0 | |||
) | |||
@@ -942,6 +942,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ | |||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | |||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | |||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | |||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= | |||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= | |||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | |||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | |||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | |||
@@ -1,29 +1,72 @@ | |||
package constant | |||
const ( | |||
VarNameProcessType string = "_ProcessType_" | |||
VarNameOperationName string = "_operation_name_" | |||
OperationNameStart string = "start" | |||
VarNameAsyncCallback string = "_async_callback_" | |||
VarNameStateMachineInst string = "_current_statemachine_instance_" | |||
VarNameStateMachine string = "_current_statemachine_" | |||
VarNameStateMachineEngine string = "_current_statemachine_engine_" | |||
VarNameStateMachineConfig string = "_statemachine_config_" | |||
VarNameStateMachineContext string = "context" | |||
VarNameIsAsyncExecution string = "_is_async_execution_" | |||
VarNameStateInst string = "_current_state_instance_" | |||
SeqEntityStateMachineInst string = "STATE_MACHINE_INST" | |||
SeqEntityStateInst string = "STATE_INST" | |||
VarNameBusinesskey string = "_business_key_" | |||
VarNameParentId string = "_parent_id_" | |||
StateTypeServiceTask string = "ServiceTask" | |||
StateTypeChoice string = "Choice" | |||
StateTypeSubStateMachine string = "SubStateMachine" | |||
CompensateSubMachine string = "CompensateSubMachine" | |||
StateTypeSucceed string = "Succeed" | |||
StateTypeFail string = "Fail" | |||
StateTypeCompensationTrigger string = "CompensationTrigger" | |||
StateTypeScriptTask string = "ScriptTask" | |||
CompensateSubMachineStateNamePrefix string = "_compensate_sub_machine_state_" | |||
DefaultScriptType string = "groovy" | |||
// region State Types | |||
StateTypeServiceTask string = "ServiceTask" | |||
StateTypeChoice string = "Choice" | |||
StateTypeFail string = "Fail" | |||
StateTypeSucceed string = "Succeed" | |||
StateTypeCompensationTrigger string = "CompensationTrigger" | |||
StateTypeSubStateMachine string = "SubStateMachine" | |||
StateTypeCompensateSubMachine string = "CompensateSubMachine" | |||
StateTypeScriptTask string = "ScriptTask" | |||
StateTypeLoopStart string = "LoopStart" | |||
// end region | |||
// region Service Types | |||
ServiceTypeGRPC string = "GRPC" | |||
// end region | |||
// region System Variables | |||
VarNameOutputParams string = "outputParams" | |||
VarNameProcessType string = "_ProcessType_" | |||
VarNameOperationName string = "_operation_name_" | |||
OperationNameStart string = "start" | |||
OperationNameCompensate string = "compensate" | |||
VarNameAsyncCallback string = "_async_callback_" | |||
VarNameCurrentExceptionRoute string = "_current_exception_route_" | |||
VarNameIsExceptionNotCatch string = "_is_exception_not_catch_" | |||
VarNameSubMachineParentId string = "_sub_machine_parent_id_" | |||
VarNameCurrentChoice string = "_current_choice_" | |||
VarNameStateMachineInst string = "_current_statemachine_instance_" | |||
VarNameStateMachine string = "_current_statemachine_" | |||
VarNameStateMachineEngine string = "_current_statemachine_engine_" | |||
VarNameStateMachineConfig string = "_statemachine_config_" | |||
VarNameStateMachineContext string = "context" | |||
VarNameIsAsyncExecution string = "_is_async_execution_" | |||
VarNameStateInst string = "_current_state_instance_" | |||
VarNameBusinesskey string = "_business_key_" | |||
VarNameParentId string = "_parent_id_" | |||
VarNameCurrentException string = "currentException" | |||
CompensateSubMachineStateNamePrefix string = "_compensate_sub_machine_state_" | |||
DefaultScriptType string = "groovy" | |||
VarNameSyncExeStack string = "_sync_execution_stack_" | |||
VarNameInputParams string = "inputParams" | |||
VarNameIsLoopState string = "_is_loop_state_" | |||
VarNameCurrentCompensateTriggerState string = "_is_compensating_" | |||
VarNameCurrentCompensationHolder string = "_current_compensation_holder_" | |||
VarNameFirstCompensationStateStarted string = "_first_compensation_state_started" | |||
VarNameCurrentLoopContextHolder string = "_current_loop_context_holder_" | |||
// TODO: this lock in process context only has one, try to add more to add concurrent | |||
VarNameProcessContextMutexLock string = "_current_context_mutex_lock" | |||
VarNameFailEndStateFlag string = "_fail_end_state_flag_" | |||
// end region | |||
// region of loop | |||
LoopCounter string = "loopCounter" | |||
LoopSemaphore string = "loopSemaphore" | |||
LoopResult string = "loopResult" | |||
NumberOfInstances string = "nrOfInstances" | |||
NumberOfActiveInstances string = "nrOfActiveInstances" | |||
NumberOfCompletedInstances string = "nrOfCompletedInstances" | |||
// end region | |||
// region others | |||
SeqEntityStateMachineInst string = "STATE_MACHINE_INST" | |||
SeqEntityStateInst string = "STATE_INST" | |||
OperationNameForward string = "forward" | |||
LoopStateNamePattern string = "-loop-" | |||
// end region | |||
SeperatorParentId string = ":" | |||
) |
@@ -1,9 +1,10 @@ | |||
package process_ctrl | |||
package core | |||
import ( | |||
"context" | |||
"github.com/pkg/errors" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/constant" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/process_ctrl/process" | |||
"sync" | |||
) | |||
@@ -19,14 +20,14 @@ type DefaultBusinessProcessor struct { | |||
mu sync.RWMutex | |||
} | |||
func (d *DefaultBusinessProcessor) RegistryProcessHandler(processType ProcessType, processHandler ProcessHandler) { | |||
func (d *DefaultBusinessProcessor) RegistryProcessHandler(processType process.ProcessType, processHandler ProcessHandler) { | |||
d.mu.Lock() | |||
defer d.mu.Unlock() | |||
d.processHandlers[string(processType)] = processHandler | |||
} | |||
func (d *DefaultBusinessProcessor) RegistryRouterHandler(processType ProcessType, routerHandler RouterHandler) { | |||
func (d *DefaultBusinessProcessor) RegistryRouterHandler(processType process.ProcessType, routerHandler RouterHandler) { | |||
d.mu.Lock() | |||
defer d.mu.Unlock() | |||
@@ -55,17 +56,17 @@ func (d *DefaultBusinessProcessor) Route(ctx context.Context, processContext Pro | |||
return routerHandler.Route(ctx, processContext) | |||
} | |||
func (d *DefaultBusinessProcessor) getProcessHandler(processType ProcessType) (ProcessHandler, error) { | |||
func (d *DefaultBusinessProcessor) getProcessHandler(processType process.ProcessType) (ProcessHandler, error) { | |||
d.mu.RLock() | |||
defer d.mu.RUnlock() | |||
processHandler, ok := d.processHandlers[string(processType)] | |||
if !ok { | |||
return nil, errors.New("Cannot find process handler by type " + string(processType)) | |||
return nil, errors.New("Cannot find Process handler by type " + string(processType)) | |||
} | |||
return processHandler, nil | |||
} | |||
func (d *DefaultBusinessProcessor) getRouterHandler(processType ProcessType) (RouterHandler, error) { | |||
func (d *DefaultBusinessProcessor) getRouterHandler(processType process.ProcessType) (RouterHandler, error) { | |||
d.mu.RLock() | |||
defer d.mu.RUnlock() | |||
routerHandler, ok := d.routerHandlers[string(processType)] | |||
@@ -75,18 +76,10 @@ func (d *DefaultBusinessProcessor) getRouterHandler(processType ProcessType) (Ro | |||
return routerHandler, nil | |||
} | |||
func (d *DefaultBusinessProcessor) matchProcessType(processContext ProcessContext) ProcessType { | |||
func (d *DefaultBusinessProcessor) matchProcessType(processContext ProcessContext) process.ProcessType { | |||
ok := processContext.HasVariable(constant.VarNameProcessType) | |||
if ok { | |||
return processContext.GetVariable(constant.VarNameProcessType).(ProcessType) | |||
return processContext.GetVariable(constant.VarNameProcessType).(process.ProcessType) | |||
} | |||
return StateLang | |||
} | |||
type ProcessHandler interface { | |||
Process(ctx context.Context, processContext ProcessContext) error | |||
} | |||
type RouterHandler interface { | |||
Route(ctx context.Context, processContext ProcessContext) error | |||
return process.StateLang | |||
} |
@@ -0,0 +1,63 @@ | |||
package core | |||
import ( | |||
"context" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/constant" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||
"github.com/seata/seata-go/pkg/util/collection" | |||
"sync" | |||
) | |||
type CompensationHolder struct { | |||
statesNeedCompensation *sync.Map | |||
statesForCompensation *sync.Map | |||
stateStackNeedCompensation *collection.Stack | |||
} | |||
func (c *CompensationHolder) StatesNeedCompensation() *sync.Map { | |||
return c.statesNeedCompensation | |||
} | |||
func (c *CompensationHolder) SetStatesNeedCompensation(statesNeedCompensation *sync.Map) { | |||
c.statesNeedCompensation = statesNeedCompensation | |||
} | |||
func (c *CompensationHolder) StatesForCompensation() *sync.Map { | |||
return c.statesForCompensation | |||
} | |||
func (c *CompensationHolder) SetStatesForCompensation(statesForCompensation *sync.Map) { | |||
c.statesForCompensation = statesForCompensation | |||
} | |||
func (c *CompensationHolder) StateStackNeedCompensation() *collection.Stack { | |||
return c.stateStackNeedCompensation | |||
} | |||
func (c *CompensationHolder) SetStateStackNeedCompensation(stateStackNeedCompensation *collection.Stack) { | |||
c.stateStackNeedCompensation = stateStackNeedCompensation | |||
} | |||
func (c *CompensationHolder) AddToBeCompensatedState(stateName string, toBeCompensatedState statelang.StateInstance) { | |||
c.statesNeedCompensation.Store(stateName, toBeCompensatedState) | |||
} | |||
func NewCompensationHolder() *CompensationHolder { | |||
return &CompensationHolder{ | |||
statesNeedCompensation: &sync.Map{}, | |||
statesForCompensation: &sync.Map{}, | |||
stateStackNeedCompensation: collection.NewStack(), | |||
} | |||
} | |||
func GetCurrentCompensationHolder(ctx context.Context, processContext ProcessContext, forceCreate bool) *CompensationHolder { | |||
compensationholder := processContext.GetVariable(constant.VarNameCurrentCompensationHolder).(*CompensationHolder) | |||
lock := processContext.GetVariable(constant.VarNameProcessContextMutexLock).(*sync.Mutex) | |||
lock.Lock() | |||
defer lock.Unlock() | |||
if compensationholder == nil && forceCreate { | |||
compensationholder = NewCompensationHolder() | |||
processContext.SetVariable(constant.VarNameCurrentCompensationHolder, compensationholder) | |||
} | |||
return compensationholder | |||
} |
@@ -0,0 +1,199 @@ | |||
package core | |||
import ( | |||
"github.com/seata/seata-go/pkg/saga/statemachine/engine/expr" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/engine/invoker" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/engine/sequence" | |||
"sync" | |||
) | |||
const ( | |||
DefaultTransOperTimeout = 60000 * 30 | |||
DefaultServiceInvokeTimeout = 60000 * 5 | |||
) | |||
type DefaultStateMachineConfig struct { | |||
// Configuration | |||
transOperationTimeout int | |||
serviceInvokeTimeout int | |||
charset string | |||
defaultTenantId string | |||
// Components | |||
// Event publisher | |||
syncProcessCtrlEventPublisher EventPublisher | |||
asyncProcessCtrlEventPublisher EventPublisher | |||
// Store related components | |||
stateLogRepository StateLogRepository | |||
stateLogStore StateLogStore | |||
stateLangStore StateLangStore | |||
stateMachineRepository StateMachineRepository | |||
// Expression related components | |||
expressionFactoryManager expr.ExpressionFactoryManager | |||
expressionResolver expr.ExpressionResolver | |||
// Invoker related components | |||
serviceInvokerManager invoker.ServiceInvokerManager | |||
scriptInvokerManager invoker.ScriptInvokerManager | |||
// Other components | |||
statusDecisionStrategy StatusDecisionStrategy | |||
seqGenerator sequence.SeqGenerator | |||
componentLock *sync.Mutex | |||
} | |||
func (c *DefaultStateMachineConfig) ComponentLock() *sync.Mutex { | |||
return c.componentLock | |||
} | |||
func (c *DefaultStateMachineConfig) SetComponentLock(componentLock *sync.Mutex) { | |||
c.componentLock = componentLock | |||
} | |||
func (c *DefaultStateMachineConfig) SetTransOperationTimeout(transOperationTimeout int) { | |||
c.transOperationTimeout = transOperationTimeout | |||
} | |||
func (c *DefaultStateMachineConfig) SetServiceInvokeTimeout(serviceInvokeTimeout int) { | |||
c.serviceInvokeTimeout = serviceInvokeTimeout | |||
} | |||
func (c *DefaultStateMachineConfig) SetCharset(charset string) { | |||
c.charset = charset | |||
} | |||
func (c *DefaultStateMachineConfig) SetDefaultTenantId(defaultTenantId string) { | |||
c.defaultTenantId = defaultTenantId | |||
} | |||
func (c *DefaultStateMachineConfig) SetSyncProcessCtrlEventPublisher(syncProcessCtrlEventPublisher EventPublisher) { | |||
c.syncProcessCtrlEventPublisher = syncProcessCtrlEventPublisher | |||
} | |||
func (c *DefaultStateMachineConfig) SetAsyncProcessCtrlEventPublisher(asyncProcessCtrlEventPublisher EventPublisher) { | |||
c.asyncProcessCtrlEventPublisher = asyncProcessCtrlEventPublisher | |||
} | |||
func (c *DefaultStateMachineConfig) SetStateLogRepository(stateLogRepository StateLogRepository) { | |||
c.stateLogRepository = stateLogRepository | |||
} | |||
func (c *DefaultStateMachineConfig) SetStateLogStore(stateLogStore StateLogStore) { | |||
c.stateLogStore = stateLogStore | |||
} | |||
func (c *DefaultStateMachineConfig) SetStateLangStore(stateLangStore StateLangStore) { | |||
c.stateLangStore = stateLangStore | |||
} | |||
func (c *DefaultStateMachineConfig) SetStateMachineRepository(stateMachineRepository StateMachineRepository) { | |||
c.stateMachineRepository = stateMachineRepository | |||
} | |||
func (c *DefaultStateMachineConfig) SetExpressionFactoryManager(expressionFactoryManager expr.ExpressionFactoryManager) { | |||
c.expressionFactoryManager = expressionFactoryManager | |||
} | |||
func (c *DefaultStateMachineConfig) SetExpressionResolver(expressionResolver expr.ExpressionResolver) { | |||
c.expressionResolver = expressionResolver | |||
} | |||
func (c *DefaultStateMachineConfig) SetServiceInvokerManager(serviceInvokerManager invoker.ServiceInvokerManager) { | |||
c.serviceInvokerManager = serviceInvokerManager | |||
} | |||
func (c *DefaultStateMachineConfig) SetScriptInvokerManager(scriptInvokerManager invoker.ScriptInvokerManager) { | |||
c.scriptInvokerManager = scriptInvokerManager | |||
} | |||
func (c *DefaultStateMachineConfig) SetStatusDecisionStrategy(statusDecisionStrategy StatusDecisionStrategy) { | |||
c.statusDecisionStrategy = statusDecisionStrategy | |||
} | |||
func (c *DefaultStateMachineConfig) SetSeqGenerator(seqGenerator sequence.SeqGenerator) { | |||
c.seqGenerator = seqGenerator | |||
} | |||
func (c *DefaultStateMachineConfig) StateLogRepository() StateLogRepository { | |||
return c.stateLogRepository | |||
} | |||
func (c *DefaultStateMachineConfig) StateMachineRepository() StateMachineRepository { | |||
return c.stateMachineRepository | |||
} | |||
func (c *DefaultStateMachineConfig) StateLogStore() StateLogStore { | |||
return c.stateLogStore | |||
} | |||
func (c *DefaultStateMachineConfig) StateLangStore() StateLangStore { | |||
return c.stateLangStore | |||
} | |||
func (c *DefaultStateMachineConfig) ExpressionFactoryManager() expr.ExpressionFactoryManager { | |||
return c.expressionFactoryManager | |||
} | |||
func (c *DefaultStateMachineConfig) ExpressionResolver() expr.ExpressionResolver { | |||
return c.expressionResolver | |||
} | |||
func (c *DefaultStateMachineConfig) SeqGenerator() sequence.SeqGenerator { | |||
return c.seqGenerator | |||
} | |||
func (c *DefaultStateMachineConfig) StatusDecisionStrategy() StatusDecisionStrategy { | |||
return c.statusDecisionStrategy | |||
} | |||
func (c *DefaultStateMachineConfig) EventPublisher() EventPublisher { | |||
return c.syncProcessCtrlEventPublisher | |||
} | |||
func (c *DefaultStateMachineConfig) AsyncEventPublisher() EventPublisher { | |||
return c.asyncProcessCtrlEventPublisher | |||
} | |||
func (c *DefaultStateMachineConfig) ServiceInvokerManager() invoker.ServiceInvokerManager { | |||
return c.serviceInvokerManager | |||
} | |||
func (c *DefaultStateMachineConfig) ScriptInvokerManager() invoker.ScriptInvokerManager { | |||
return c.scriptInvokerManager | |||
} | |||
func (c *DefaultStateMachineConfig) CharSet() string { | |||
return c.charset | |||
} | |||
func (c *DefaultStateMachineConfig) SetCharSet(charset string) { | |||
c.charset = charset | |||
} | |||
func (c *DefaultStateMachineConfig) DefaultTenantId() string { | |||
return c.defaultTenantId | |||
} | |||
func (c *DefaultStateMachineConfig) TransOperationTimeout() int { | |||
return c.transOperationTimeout | |||
} | |||
func (c *DefaultStateMachineConfig) ServiceInvokeTimeout() int { | |||
return c.serviceInvokeTimeout | |||
} | |||
func NewDefaultStateMachineConfig() *DefaultStateMachineConfig { | |||
c := &DefaultStateMachineConfig{ | |||
transOperationTimeout: DefaultTransOperTimeout, | |||
serviceInvokeTimeout: DefaultServiceInvokeTimeout, | |||
charset: "UTF-8", | |||
defaultTenantId: "000001", | |||
componentLock: &sync.Mutex{}, | |||
} | |||
// TODO: init config | |||
return c | |||
} |
@@ -0,0 +1,149 @@ | |||
package core | |||
import ( | |||
"context" | |||
"errors" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/constant" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/statelang/state" | |||
"github.com/seata/seata-go/pkg/util/log" | |||
"golang.org/x/sync/semaphore" | |||
"reflect" | |||
"strings" | |||
"sync" | |||
"time" | |||
) | |||
func EndStateMachine(ctx context.Context, processContext ProcessContext) error { | |||
if processContext.HasVariable(constant.VarNameIsLoopState) { | |||
if processContext.HasVariable(constant.LoopSemaphore) { | |||
weighted, ok := processContext.GetVariable(constant.LoopSemaphore).(semaphore.Weighted) | |||
if !ok { | |||
return errors.New("semaphore type is not weighted") | |||
} | |||
weighted.Release(1) | |||
} | |||
} | |||
stateMachineInstance, ok := processContext.GetVariable(constant.VarNameStateMachineInst).(statelang.StateMachineInstance) | |||
if !ok { | |||
return errors.New("state machine instance type is not statelang.StateMachineInstance") | |||
} | |||
stateMachineInstance.SetEndTime(time.Now()) | |||
exp, ok := processContext.GetVariable(constant.VarNameCurrentException).(error) | |||
if !ok { | |||
return errors.New("exception type is not error") | |||
} | |||
if exp != nil { | |||
stateMachineInstance.SetException(exp) | |||
log.Debugf("Exception Occurred: %s", exp) | |||
} | |||
stateMachineConfig, ok := processContext.GetVariable(constant.VarNameStateMachineConfig).(StateMachineConfig) | |||
if err := stateMachineConfig.StatusDecisionStrategy().DecideOnEndState(ctx, processContext, stateMachineInstance, exp); err != nil { | |||
return err | |||
} | |||
contextParams, ok := processContext.GetVariable(constant.VarNameStateMachineContext).(map[string]interface{}) | |||
if !ok { | |||
return errors.New("state machine context type is not map[string]interface{}") | |||
} | |||
endParams := stateMachineInstance.EndParams() | |||
for k, v := range contextParams { | |||
endParams[k] = v | |||
} | |||
stateMachineInstance.SetEndParams(endParams) | |||
stateInstruction, ok := processContext.GetInstruction().(StateInstruction) | |||
if !ok { | |||
return errors.New("state instruction type is not process_ctrl.StateInstruction") | |||
} | |||
stateInstruction.SetEnd(true) | |||
stateMachineInstance.SetRunning(false) | |||
stateMachineInstance.SetEndTime(time.Now()) | |||
if stateMachineInstance.StateMachine().IsPersist() && stateMachineConfig.StateLangStore() != nil { | |||
err := stateMachineConfig.StateLogStore().RecordStateMachineFinished(ctx, stateMachineInstance, processContext) | |||
if err != nil { | |||
return err | |||
} | |||
} | |||
callBack, ok := processContext.GetVariable(constant.VarNameAsyncCallback).(CallBack) | |||
if ok { | |||
if exp != nil { | |||
callBack.OnError(ctx, processContext, stateMachineInstance, exp) | |||
} else { | |||
callBack.OnFinished(ctx, processContext, stateMachineInstance) | |||
} | |||
} | |||
return nil | |||
} | |||
func HandleException(processContext ProcessContext, abstractTaskState *state.AbstractTaskState, err error) { | |||
catches := abstractTaskState.Catches() | |||
if catches != nil && len(catches) != 0 { | |||
for _, exceptionMatch := range catches { | |||
exceptions := exceptionMatch.Exceptions() | |||
exceptionTypes := exceptionMatch.ExceptionTypes() | |||
if exceptions != nil && len(exceptions) != 0 { | |||
if exceptionTypes == nil { | |||
lock := processContext.GetVariable(constant.VarNameProcessContextMutexLock).(*sync.Mutex) | |||
lock.Lock() | |||
defer lock.Unlock() | |||
error := errors.New("") | |||
for i := 0; i < len(exceptions); i++ { | |||
exceptionTypes = append(exceptionTypes, reflect.TypeOf(error)) | |||
} | |||
} | |||
exceptionMatch.SetExceptionTypes(exceptionTypes) | |||
} | |||
for i, _ := range exceptionTypes { | |||
if reflect.TypeOf(err) == exceptionTypes[i] { | |||
// HACK: we can not get error type in config file during runtime, so we use exception str | |||
if strings.Contains(err.Error(), exceptions[i]) { | |||
hierarchicalProcessContext := processContext.(HierarchicalProcessContext) | |||
hierarchicalProcessContext.SetVariable(constant.VarNameCurrentExceptionRoute, exceptionMatch.Next()) | |||
return | |||
} | |||
} | |||
} | |||
} | |||
} | |||
log.Error("Task execution failed and no catches configured") | |||
hierarchicalProcessContext := processContext.(HierarchicalProcessContext) | |||
hierarchicalProcessContext.SetVariable(constant.VarNameIsExceptionNotCatch, true) | |||
} | |||
// GetOriginStateName get origin state name without suffix like fork | |||
func GetOriginStateName(stateInstance statelang.StateInstance) string { | |||
stateName := stateInstance.Name() | |||
if stateName != "" { | |||
end := strings.LastIndex(stateName, constant.LoopStateNamePattern) | |||
if end > -1 { | |||
return stateName[:end+1] | |||
} | |||
} | |||
return stateName | |||
} | |||
// IsTimeout test if is timeout | |||
func IsTimeout(gmtUpdated time.Time, timeoutMillis int) bool { | |||
if timeoutMillis < 0 { | |||
return false | |||
} | |||
return time.Now().Unix()-gmtUpdated.Unix() > int64(timeoutMillis) | |||
} | |||
func GenerateParentId(stateInstance statelang.StateInstance) string { | |||
return stateInstance.MachineInstanceID() + constant.SeperatorParentId + stateInstance.ID() | |||
} |
@@ -1,4 +1,4 @@ | |||
package events | |||
package core | |||
type Event interface { | |||
} |
@@ -0,0 +1,113 @@ | |||
package core | |||
import ( | |||
"context" | |||
"fmt" | |||
"github.com/pkg/errors" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/constant" | |||
"github.com/seata/seata-go/pkg/util/collection" | |||
"github.com/seata/seata-go/pkg/util/log" | |||
) | |||
type EventBus interface { | |||
Offer(ctx context.Context, event Event) (bool, error) | |||
EventConsumerList(event Event) []EventConsumer | |||
RegisterEventConsumer(consumer EventConsumer) | |||
} | |||
type BaseEventBus struct { | |||
eventConsumerList []EventConsumer | |||
} | |||
func (b *BaseEventBus) RegisterEventConsumer(consumer EventConsumer) { | |||
if b.eventConsumerList == nil { | |||
b.eventConsumerList = make([]EventConsumer, 0) | |||
} | |||
b.eventConsumerList = append(b.eventConsumerList, consumer) | |||
} | |||
func (b *BaseEventBus) EventConsumerList(event Event) []EventConsumer { | |||
var acceptedConsumerList = make([]EventConsumer, 0) | |||
for i := range b.eventConsumerList { | |||
eventConsumer := b.eventConsumerList[i] | |||
if eventConsumer.Accept(event) { | |||
acceptedConsumerList = append(acceptedConsumerList, eventConsumer) | |||
} | |||
} | |||
return acceptedConsumerList | |||
} | |||
type DirectEventBus struct { | |||
BaseEventBus | |||
} | |||
func (d DirectEventBus) Offer(ctx context.Context, event Event) (bool, error) { | |||
eventConsumerList := d.EventConsumerList(event) | |||
if len(eventConsumerList) == 0 { | |||
log.Debugf("cannot find event handler by type: %T", event) | |||
return false, nil | |||
} | |||
isFirstEvent := true | |||
processContext, ok := event.(ProcessContext) | |||
if !ok { | |||
log.Errorf("event %T is illegal, required process_ctrl.ProcessContext", event) | |||
return false, nil | |||
} | |||
stack := processContext.GetVariable(constant.VarNameSyncExeStack).(*collection.Stack) | |||
if stack == nil { | |||
stack = collection.NewStack() | |||
processContext.SetVariable(constant.VarNameSyncExeStack, stack) | |||
isFirstEvent = true | |||
} | |||
stack.Push(processContext) | |||
if isFirstEvent { | |||
for stack.Len() > 0 { | |||
currentContext := stack.Pop().(ProcessContext) | |||
for _, eventConsumer := range eventConsumerList { | |||
err := eventConsumer.Process(ctx, currentContext) | |||
if err != nil { | |||
log.Errorf("process event %T error: %s", event, err.Error()) | |||
return false, err | |||
} | |||
} | |||
} | |||
} | |||
return true, nil | |||
} | |||
type AsyncEventBus struct { | |||
BaseEventBus | |||
} | |||
func (a AsyncEventBus) Offer(ctx context.Context, event Event) (bool, error) { | |||
eventConsumerList := a.EventConsumerList(event) | |||
if len(eventConsumerList) == 0 { | |||
errStr := fmt.Sprintf("cannot find event handler by type: %T", event) | |||
log.Errorf(errStr) | |||
return false, errors.New(errStr) | |||
} | |||
processContext, ok := event.(ProcessContext) | |||
if !ok { | |||
errStr := fmt.Sprintf("event %T is illegal, required process_ctrl.ProcessContext", event) | |||
log.Errorf(errStr) | |||
return false, errors.New(errStr) | |||
} | |||
for _, eventConsumer := range eventConsumerList { | |||
go func() { | |||
err := eventConsumer.Process(ctx, processContext) | |||
if err != nil { | |||
log.Errorf("process event %T error: %s", event, err.Error()) | |||
} | |||
}() | |||
} | |||
return true, nil | |||
} |
@@ -1,8 +1,9 @@ | |||
package events | |||
package core | |||
import ( | |||
"context" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/engine/process_ctrl" | |||
"fmt" | |||
"github.com/pkg/errors" | |||
) | |||
type EventConsumer interface { | |||
@@ -12,6 +13,7 @@ type EventConsumer interface { | |||
} | |||
type ProcessCtrlEventConsumer struct { | |||
processController ProcessController | |||
} | |||
func (p ProcessCtrlEventConsumer) Accept(event Event) bool { | |||
@@ -19,11 +21,14 @@ func (p ProcessCtrlEventConsumer) Accept(event Event) bool { | |||
return false | |||
} | |||
_, ok := event.(process_ctrl.ProcessContext) | |||
_, ok := event.(ProcessContext) | |||
return ok | |||
} | |||
func (p ProcessCtrlEventConsumer) Process(ctx context.Context, event Event) error { | |||
//TODO implement me | |||
panic("implement me") | |||
processContext, ok := event.(ProcessContext) | |||
if !ok { | |||
return errors.New(fmt.Sprint("event %T is illegal, required process_ctrl.ProcessContext", event)) | |||
} | |||
return p.processController.Process(ctx, processContext) | |||
} |
@@ -1,4 +1,4 @@ | |||
package events | |||
package core | |||
import "context" | |||
@@ -0,0 +1,96 @@ | |||
package core | |||
import ( | |||
"errors" | |||
"fmt" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/constant" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||
) | |||
type Instruction interface { | |||
} | |||
type StateInstruction struct { | |||
stateName string | |||
stateMachineName string | |||
tenantId string | |||
end bool | |||
temporaryState statelang.State | |||
} | |||
func NewStateInstruction(stateMachineName string, tenantId string) *StateInstruction { | |||
return &StateInstruction{stateMachineName: stateMachineName, tenantId: tenantId} | |||
} | |||
func (s *StateInstruction) StateName() string { | |||
return s.stateName | |||
} | |||
func (s *StateInstruction) SetStateName(stateName string) { | |||
s.stateName = stateName | |||
} | |||
func (s *StateInstruction) StateMachineName() string { | |||
return s.stateMachineName | |||
} | |||
func (s *StateInstruction) SetStateMachineName(stateMachineName string) { | |||
s.stateMachineName = stateMachineName | |||
} | |||
func (s *StateInstruction) TenantId() string { | |||
return s.tenantId | |||
} | |||
func (s *StateInstruction) SetTenantId(tenantId string) { | |||
s.tenantId = tenantId | |||
} | |||
func (s *StateInstruction) End() bool { | |||
return s.end | |||
} | |||
func (s *StateInstruction) SetEnd(end bool) { | |||
s.end = end | |||
} | |||
func (s *StateInstruction) TemporaryState() statelang.State { | |||
return s.temporaryState | |||
} | |||
func (s *StateInstruction) SetTemporaryState(temporaryState statelang.State) { | |||
s.temporaryState = temporaryState | |||
} | |||
func (s *StateInstruction) GetState(context ProcessContext) (statelang.State, error) { | |||
if s.temporaryState != nil { | |||
return s.temporaryState, nil | |||
} | |||
if s.stateMachineName == "" { | |||
return nil, errors.New("stateMachineName is required") | |||
} | |||
stateMachineConfig, ok := context.GetVariable(constant.VarNameStateMachineConfig).(StateMachineConfig) | |||
if !ok { | |||
return nil, errors.New("stateMachineConfig is required in context") | |||
} | |||
stateMachine, err := stateMachineConfig.StateMachineRepository().GetLastVersionStateMachine(s.stateMachineName, s.tenantId) | |||
if err != nil { | |||
return nil, errors.New("get stateMachine in state machine repository error") | |||
} | |||
if stateMachine == nil { | |||
return nil, errors.New(fmt.Sprintf("stateMachine [%s] is not exist", s.stateMachineName)) | |||
} | |||
if s.stateName == "" { | |||
s.stateName = stateMachine.StartState() | |||
} | |||
state := stateMachine.States()[s.stateName] | |||
if state == nil { | |||
return nil, errors.New(fmt.Sprintf("state [%s] is not exist", s.stateName)) | |||
} | |||
return state, nil | |||
} |
@@ -0,0 +1,92 @@ | |||
package core | |||
import ( | |||
"context" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/constant" | |||
"sync" | |||
) | |||
type LoopContextHolder struct { | |||
nrOfInstances int32 | |||
nrOfActiveInstances int32 | |||
nrOfCompletedInstances int32 | |||
failEnd bool | |||
completionConditionSatisfied bool | |||
loopCounterStack []int | |||
forwardCounterStack []int | |||
collection interface{} | |||
} | |||
func NewLoopContextHolder() *LoopContextHolder { | |||
return &LoopContextHolder{ | |||
nrOfInstances: 0, | |||
nrOfActiveInstances: 0, | |||
nrOfCompletedInstances: 0, | |||
failEnd: false, | |||
completionConditionSatisfied: false, | |||
loopCounterStack: make([]int, 0), | |||
forwardCounterStack: make([]int, 0), | |||
collection: nil, | |||
} | |||
} | |||
func GetCurrentLoopContextHolder(ctx context.Context, processContext ProcessContext, forceCreate bool) *LoopContextHolder { | |||
mutex := processContext.GetVariable(constant.VarNameProcessContextMutexLock).(*sync.Mutex) | |||
mutex.Lock() | |||
defer mutex.Unlock() | |||
loopContextHolder := processContext.GetVariable(constant.VarNameCurrentLoopContextHolder).(*LoopContextHolder) | |||
if loopContextHolder == nil && forceCreate { | |||
loopContextHolder = &LoopContextHolder{} | |||
processContext.SetVariable(constant.VarNameCurrentLoopContextHolder, loopContextHolder) | |||
} | |||
return loopContextHolder | |||
} | |||
func ClearCurrent(ctx context.Context, processContext ProcessContext) { | |||
processContext.RemoveVariable(constant.VarNameCurrentLoopContextHolder) | |||
} | |||
func (l *LoopContextHolder) NrOfInstances() int32 { | |||
return l.nrOfInstances | |||
} | |||
func (l *LoopContextHolder) NrOfActiveInstances() int32 { | |||
return l.nrOfActiveInstances | |||
} | |||
func (l *LoopContextHolder) NrOfCompletedInstances() int32 { | |||
return l.nrOfCompletedInstances | |||
} | |||
func (l *LoopContextHolder) FailEnd() bool { | |||
return l.failEnd | |||
} | |||
func (l *LoopContextHolder) SetFailEnd(failEnd bool) { | |||
l.failEnd = failEnd | |||
} | |||
func (l *LoopContextHolder) CompletionConditionSatisfied() bool { | |||
return l.completionConditionSatisfied | |||
} | |||
func (l *LoopContextHolder) SetCompletionConditionSatisfied(completionConditionSatisfied bool) { | |||
l.completionConditionSatisfied = completionConditionSatisfied | |||
} | |||
func (l *LoopContextHolder) LoopCounterStack() []int { | |||
return l.loopCounterStack | |||
} | |||
func (l *LoopContextHolder) ForwardCounterStack() []int { | |||
return l.forwardCounterStack | |||
} | |||
func (l *LoopContextHolder) Collection() interface{} { | |||
return l.collection | |||
} | |||
func (l *LoopContextHolder) SetCollection(collection interface{}) { | |||
l.collection = collection | |||
} |
@@ -0,0 +1,40 @@ | |||
package core | |||
import ( | |||
"context" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/constant" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/statelang/state" | |||
"github.com/seata/seata-go/pkg/util/log" | |||
) | |||
func GetLoopConfig(ctx context.Context, processContext ProcessContext, currentState statelang.State) state.Loop { | |||
if matchLoop(currentState) { | |||
taskState := currentState.(state.AbstractTaskState) | |||
stateMachineInstance := processContext.GetVariable(constant.VarNameStateMachineInst).(statelang.StateMachineInstance) | |||
stateMachineConfig := processContext.GetVariable(constant.VarNameStateMachineConfig).(StateMachineConfig) | |||
if taskState.Loop() != nil { | |||
loop := taskState.Loop() | |||
collectionName := loop.Collection() | |||
if collectionName != "" { | |||
expression := CreateValueExpression(stateMachineConfig.ExpressionResolver(), collectionName) | |||
collection := GetValue(expression, stateMachineInstance.Context(), nil) | |||
collectionList := collection.([]any) | |||
if len(collectionList) > 0 { | |||
current := GetCurrentLoopContextHolder(ctx, processContext, true) | |||
current.SetCollection(collection) | |||
return loop | |||
} | |||
} | |||
log.Warn("State [{}] loop collection param [{}] invalid", currentState.Name(), collectionName) | |||
} | |||
} | |||
return nil | |||
} | |||
func matchLoop(currentState statelang.State) bool { | |||
return currentState != nil && (constant.StateTypeServiceTask == currentState.Type() || | |||
constant.StateTypeScriptTask == currentState.Type() || constant.StateTypeSubStateMachine == currentState.Type()) | |||
} |
@@ -0,0 +1,132 @@ | |||
package core | |||
import ( | |||
"fmt" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/constant" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/engine/expr" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/statelang/state" | |||
"strings" | |||
"sync" | |||
) | |||
func CreateInputParams(processContext ProcessContext, expressionResolver expr.ExpressionResolver, | |||
stateInstance *statelang.StateInstanceImpl, serviceTaskState *state.AbstractTaskState, variablesFrom any) []any { | |||
inputAssignments := serviceTaskState.Input() | |||
if inputAssignments == nil || len(inputAssignments) == 0 { | |||
return inputAssignments | |||
} | |||
inputExpressions := serviceTaskState.InputExpressions() | |||
if inputExpressions == nil || len(inputExpressions) == 0 { | |||
lock := processContext.GetVariable(constant.VarNameProcessContextMutexLock).(*sync.Mutex) | |||
lock.Lock() | |||
defer lock.Unlock() | |||
inputExpressions = serviceTaskState.InputExpressions() | |||
if inputExpressions == nil || len(inputExpressions) == 0 { | |||
inputExpressions = make([]any, 0, len(inputAssignments)) | |||
for _, assignment := range inputAssignments { | |||
inputExpressions = append(inputExpressions, CreateValueExpression(expressionResolver, assignment)) | |||
} | |||
} | |||
serviceTaskState.SetInputExpressions(inputExpressions) | |||
} | |||
inputValues := make([]any, 0, len(inputExpressions)) | |||
for _, valueExpression := range inputExpressions { | |||
value := GetValue(valueExpression, variablesFrom, stateInstance) | |||
inputValues = append(inputValues, value) | |||
} | |||
return inputValues | |||
} | |||
func CreateOutputParams(config StateMachineConfig, expressionResolver expr.ExpressionResolver, | |||
serviceTaskState *state.AbstractTaskState, variablesFrom any) (map[string]any, error) { | |||
outputAssignments := serviceTaskState.Output() | |||
if outputAssignments == nil || len(outputAssignments) == 0 { | |||
return make(map[string]any, 0), nil | |||
} | |||
outputExpressions := serviceTaskState.OutputExpressions() | |||
if outputExpressions == nil { | |||
config.ComponentLock().Lock() | |||
defer config.ComponentLock().Unlock() | |||
outputExpressions = serviceTaskState.OutputExpressions() | |||
if outputExpressions == nil { | |||
outputExpressions = make(map[string]any, len(outputAssignments)) | |||
for key, value := range outputAssignments { | |||
outputExpressions[key] = CreateValueExpression(expressionResolver, value) | |||
} | |||
} | |||
serviceTaskState.SetOutputExpressions(outputExpressions) | |||
} | |||
outputValues := make(map[string]any, len(outputExpressions)) | |||
for paramName, _ := range outputExpressions { | |||
outputValues[paramName] = GetValue(outputExpressions[paramName], variablesFrom, nil) | |||
} | |||
return outputValues, nil | |||
} | |||
func CreateValueExpression(expressionResolver expr.ExpressionResolver, paramAssignment any) any { | |||
var valueExpression any | |||
switch paramAssignment.(type) { | |||
case expr.Expression: | |||
valueExpression = paramAssignment | |||
case map[string]any: | |||
paramMapAssignment := paramAssignment.(map[string]any) | |||
paramMap := make(map[string]any, len(paramMapAssignment)) | |||
for key, value := range paramMapAssignment { | |||
paramMap[key] = CreateValueExpression(expressionResolver, value) | |||
} | |||
valueExpression = paramMap | |||
case []any: | |||
paramListAssignment := paramAssignment.([]any) | |||
paramList := make([]any, 0, len(paramListAssignment)) | |||
for _, value := range paramListAssignment { | |||
paramList = append(paramList, CreateValueExpression(expressionResolver, value)) | |||
} | |||
valueExpression = paramList | |||
case string: | |||
value := paramAssignment.(string) | |||
if !strings.HasPrefix(value, "$") { | |||
valueExpression = paramAssignment | |||
} | |||
valueExpression = expressionResolver.Expression(value) | |||
default: | |||
valueExpression = paramAssignment | |||
} | |||
return valueExpression | |||
} | |||
func GetValue(valueExpression any, variablesFrom any, stateInstance statelang.StateInstance) any { | |||
switch valueExpression.(type) { | |||
case expr.Expression: | |||
expression := valueExpression.(expr.Expression) | |||
value := expression.Value(variablesFrom) | |||
if _, ok := valueExpression.(expr.SequenceExpression); value != nil && stateInstance != nil && stateInstance.BusinessKey() == "" && ok { | |||
stateInstance.SetBusinessKey(fmt.Sprintf("%v", value)) | |||
} | |||
return value | |||
case map[string]any: | |||
mapValueExpression := valueExpression.(map[string]any) | |||
mapValue := make(map[string]any, len(mapValueExpression)) | |||
for key, value := range mapValueExpression { | |||
value = GetValue(value, variablesFrom, stateInstance) | |||
if value != nil { | |||
mapValue[key] = value | |||
} | |||
} | |||
return mapValue | |||
case []any: | |||
valueExpressionList := valueExpression.([]any) | |||
listValue := make([]any, 0, len(valueExpression.([]any))) | |||
for i, _ := range valueExpressionList { | |||
listValue = append(listValue, GetValue(valueExpressionList[i], variablesFrom, stateInstance)) | |||
} | |||
return listValue | |||
default: | |||
return valueExpression | |||
} | |||
} |
@@ -1,4 +1,4 @@ | |||
package process_ctrl | |||
package core | |||
import ( | |||
"sync" |
@@ -0,0 +1,31 @@ | |||
package core | |||
import ( | |||
"context" | |||
) | |||
type ProcessController interface { | |||
Process(ctx context.Context, context ProcessContext) error | |||
} | |||
type ProcessControllerImpl struct { | |||
businessProcessor BusinessProcessor | |||
} | |||
func (p *ProcessControllerImpl) Process(ctx context.Context, context ProcessContext) error { | |||
if err := p.businessProcessor.Process(ctx, context); err != nil { | |||
return err | |||
} | |||
if err := p.businessProcessor.Route(ctx, context); err != nil { | |||
return err | |||
} | |||
return nil | |||
} | |||
func (p *ProcessControllerImpl) BusinessProcessor() BusinessProcessor { | |||
return p.businessProcessor | |||
} | |||
func (p *ProcessControllerImpl) SetBusinessProcessor(businessProcessor BusinessProcessor) { | |||
p.businessProcessor = businessProcessor | |||
} |
@@ -0,0 +1,424 @@ | |||
package core | |||
import ( | |||
"context" | |||
"fmt" | |||
"github.com/pkg/errors" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/constant" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/engine/exception" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/process_ctrl/process" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/statelang/state" | |||
seataErrors "github.com/seata/seata-go/pkg/util/errors" | |||
"github.com/seata/seata-go/pkg/util/log" | |||
"time" | |||
) | |||
type ProcessCtrlStateMachineEngine struct { | |||
StateMachineConfig StateMachineConfig | |||
} | |||
func NewProcessCtrlStateMachineEngine() *ProcessCtrlStateMachineEngine { | |||
return &ProcessCtrlStateMachineEngine{ | |||
StateMachineConfig: NewDefaultStateMachineConfig(), | |||
} | |||
} | |||
func (p ProcessCtrlStateMachineEngine) Start(ctx context.Context, stateMachineName string, tenantId string, startParams map[string]interface{}) (statelang.StateMachineInstance, error) { | |||
return p.startInternal(ctx, stateMachineName, tenantId, "", startParams, false, nil) | |||
} | |||
func (p ProcessCtrlStateMachineEngine) Compensate(ctx context.Context, stateMachineInstId string, | |||
replaceParams map[string]any) (statelang.StateMachineInstance, error) { | |||
return p.compensateInternal(ctx, stateMachineInstId, replaceParams, false, nil) | |||
} | |||
func (p ProcessCtrlStateMachineEngine) startInternal(ctx context.Context, stateMachineName string, tenantId string, | |||
businessKey string, startParams map[string]interface{}, async bool, callback CallBack) (statelang.StateMachineInstance, error) { | |||
if tenantId == "" { | |||
tenantId = p.StateMachineConfig.DefaultTenantId() | |||
} | |||
stateMachineInstance, err := p.createMachineInstance(stateMachineName, tenantId, businessKey, startParams) | |||
if err != nil { | |||
return nil, err | |||
} | |||
// Build the process_ctrl context. | |||
processContextBuilder := NewProcessContextBuilder(). | |||
WithProcessType(process.StateLang). | |||
WithOperationName(constant.OperationNameStart). | |||
WithAsyncCallback(callback). | |||
WithInstruction(NewStateInstruction(stateMachineName, tenantId)). | |||
WithStateMachineInstance(stateMachineInstance). | |||
WithStateMachineConfig(p.StateMachineConfig). | |||
WithStateMachineEngine(p). | |||
WithIsAsyncExecution(async) | |||
contextMap := p.copyMap(startParams) | |||
stateMachineInstance.SetContext(contextMap) | |||
processContext := processContextBuilder.WithStateMachineContextVariables(contextMap).Build() | |||
if stateMachineInstance.StateMachine().IsPersist() && p.StateMachineConfig.StateLogStore() != nil { | |||
err := p.StateMachineConfig.StateLogStore().RecordStateMachineStarted(ctx, stateMachineInstance, processContext) | |||
if err != nil { | |||
return nil, err | |||
} | |||
} | |||
if stateMachineInstance.ID() == "" { | |||
stateMachineInstance.SetID(p.StateMachineConfig.SeqGenerator().GenerateId(constant.SeqEntityStateMachineInst, "")) | |||
} | |||
var eventPublisher EventPublisher | |||
if async { | |||
eventPublisher = p.StateMachineConfig.AsyncEventPublisher() | |||
} else { | |||
eventPublisher = p.StateMachineConfig.EventPublisher() | |||
} | |||
_, err = eventPublisher.PushEvent(ctx, processContext) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return stateMachineInstance, nil | |||
} | |||
// copyMap not deep copy, so best practice: Don’t pass by reference | |||
func (p ProcessCtrlStateMachineEngine) copyMap(startParams map[string]interface{}) map[string]interface{} { | |||
copyMap := make(map[string]interface{}, len(startParams)) | |||
for k, v := range startParams { | |||
copyMap[k] = v | |||
} | |||
return copyMap | |||
} | |||
func (p ProcessCtrlStateMachineEngine) createMachineInstance(stateMachineName string, tenantId string, businessKey string, startParams map[string]interface{}) (statelang.StateMachineInstance, error) { | |||
stateMachine, err := p.StateMachineConfig.StateMachineRepository().GetLastVersionStateMachine(stateMachineName, tenantId) | |||
if err != nil { | |||
return nil, err | |||
} | |||
if stateMachine == nil { | |||
return nil, errors.New("StateMachine [" + stateMachineName + "] is not exists") | |||
} | |||
stateMachineInstance := statelang.NewStateMachineInstanceImpl() | |||
stateMachineInstance.SetStateMachine(stateMachine) | |||
stateMachineInstance.SetTenantID(tenantId) | |||
stateMachineInstance.SetBusinessKey(businessKey) | |||
stateMachineInstance.SetStartParams(startParams) | |||
if startParams != nil { | |||
if businessKey != "" { | |||
startParams[constant.VarNameBusinesskey] = businessKey | |||
} | |||
if startParams[constant.VarNameParentId] != nil { | |||
parentId, ok := startParams[constant.VarNameParentId].(string) | |||
if !ok { | |||
} | |||
stateMachineInstance.SetParentID(parentId) | |||
delete(startParams, constant.VarNameParentId) | |||
} | |||
} | |||
stateMachineInstance.SetStatus(statelang.RU) | |||
stateMachineInstance.SetRunning(true) | |||
now := time.Now() | |||
stateMachineInstance.SetStartedTime(now) | |||
stateMachineInstance.SetUpdatedTime(now) | |||
return stateMachineInstance, nil | |||
} | |||
func (p ProcessCtrlStateMachineEngine) compensateInternal(ctx context.Context, stateMachineInstId string, replaceParams map[string]any, | |||
async bool, callback CallBack) (statelang.StateMachineInstance, error) { | |||
stateMachineInstance, err := p.reloadStateMachineInstance(ctx, stateMachineInstId) | |||
if err != nil { | |||
return nil, err | |||
} | |||
if stateMachineInstance == nil { | |||
return nil, exception.NewEngineExecutionException(seataErrors.StateMachineInstanceNotExists, | |||
"StateMachineInstance is not exits", nil) | |||
} | |||
if statelang.SU == stateMachineInstance.CompensationStatus() { | |||
return stateMachineInstance, nil | |||
} | |||
if stateMachineInstance.CompensationStatus() != "" { | |||
denyStatus := make([]statelang.ExecutionStatus, 0) | |||
denyStatus = append(denyStatus, statelang.SU) | |||
p.checkStatus(ctx, stateMachineInstance, nil, denyStatus, "", stateMachineInstance.CompensationStatus(), | |||
"compensate") | |||
} | |||
if replaceParams != nil { | |||
for key, value := range replaceParams { | |||
stateMachineInstance.EndParams()[key] = value | |||
} | |||
} | |||
contextBuilder := NewProcessContextBuilder().WithProcessType(process.StateLang). | |||
WithOperationName(constant.OperationNameCompensate).WithAsyncCallback(callback). | |||
WithStateMachineInstance(stateMachineInstance). | |||
WithStateMachineConfig(p.StateMachineConfig).WithStateMachineEngine(p).WithIsAsyncExecution(async) | |||
context := contextBuilder.Build() | |||
contextVariables, err := p.getStateMachineContextVariables(ctx, stateMachineInstance) | |||
if replaceParams != nil { | |||
for key, value := range replaceParams { | |||
contextVariables[key] = value | |||
} | |||
} | |||
p.putBusinesskeyToContextariables(stateMachineInstance, contextVariables) | |||
// TODO: Here is not use sync.map, make sure whether to use it | |||
concurrentContextVariables := make(map[string]any) | |||
p.nullSafeCopy(contextVariables, concurrentContextVariables) | |||
context.SetVariable(constant.VarNameStateMachineContext, concurrentContextVariables) | |||
stateMachineInstance.SetContext(concurrentContextVariables) | |||
tempCompensationTriggerState := state.NewCompensationTriggerStateImpl() | |||
tempCompensationTriggerState.SetStateMachine(stateMachineInstance.StateMachine()) | |||
stateMachineInstance.SetRunning(true) | |||
log.Info("Operation [compensate] start. stateMachineInstance[id:" + stateMachineInstance.ID() + "]") | |||
if stateMachineInstance.StateMachine().IsPersist() { | |||
err := p.StateMachineConfig.StateLogStore().RecordStateMachineRestarted(ctx, stateMachineInstance, context) | |||
if err != nil { | |||
return nil, err | |||
} | |||
} | |||
inst := NewStateInstruction(stateMachineInstance.TenantID(), stateMachineInstance.StateMachine().Name()) | |||
inst.SetTemporaryState(tempCompensationTriggerState) | |||
context.SetInstruction(inst) | |||
if async { | |||
_, err := p.StateMachineConfig.AsyncEventPublisher().PushEvent(ctx, context) | |||
if err != nil { | |||
return nil, err | |||
} | |||
} else { | |||
_, err := p.StateMachineConfig.EventPublisher().PushEvent(ctx, context) | |||
if err != nil { | |||
return nil, err | |||
} | |||
} | |||
return stateMachineInstance, nil | |||
} | |||
func (p ProcessCtrlStateMachineEngine) reloadStateMachineInstance(ctx context.Context, instId string) (statelang.StateMachineInstance, error) { | |||
instance, err := p.StateMachineConfig.StateLogStore().GetStateMachineInstance(instId) | |||
if err != nil { | |||
return nil, err | |||
} | |||
if instance != nil { | |||
stateMachine := instance.StateMachine() | |||
if stateMachine == nil { | |||
stateMachine, err = p.StateMachineConfig.StateMachineRepository().GetStateMachineById(instance.MachineID()) | |||
if err != nil { | |||
return nil, err | |||
} | |||
instance.SetStateMachine(stateMachine) | |||
} | |||
if stateMachine == nil { | |||
return nil, exception.NewEngineExecutionException(seataErrors.ObjectNotExists, | |||
"StateMachine[id:"+instance.MachineID()+"] not exist.", nil) | |||
} | |||
stateList := instance.StateList() | |||
if stateList == nil || len(stateList) == 0 { | |||
stateList, err = p.StateMachineConfig.StateLogStore().GetStateInstanceListByMachineInstanceId(instId) | |||
if err != nil { | |||
return nil, err | |||
} | |||
if stateList != nil && len(stateList) > 0 { | |||
for _, tmpStateInstance := range stateList { | |||
instance.PutState(tmpStateInstance.ID(), tmpStateInstance) | |||
} | |||
} | |||
} | |||
if instance.EndParams() == nil || len(instance.EndParams()) == 0 { | |||
variables, err := p.replayContextVariables(ctx, instance) | |||
if err != nil { | |||
return nil, err | |||
} | |||
instance.SetEndParams(variables) | |||
} | |||
} | |||
return instance, nil | |||
} | |||
func (p ProcessCtrlStateMachineEngine) replayContextVariables(ctx context.Context, stateMachineInstance statelang.StateMachineInstance) (map[string]any, error) { | |||
contextVariables := make(map[string]any) | |||
if stateMachineInstance.StartParams() != nil { | |||
for key, value := range stateMachineInstance.StartParams() { | |||
contextVariables[key] = value | |||
} | |||
} | |||
stateInstanceList := stateMachineInstance.StateList() | |||
if stateInstanceList == nil || len(stateInstanceList) == 0 { | |||
return contextVariables, nil | |||
} | |||
for _, stateInstance := range stateInstanceList { | |||
serviceOutputParams := stateInstance.OutputParams() | |||
if serviceOutputParams != nil { | |||
serviceTaskStateImpl, ok := stateMachineInstance.StateMachine().State(GetOriginStateName(stateInstance)).(*state.ServiceTaskStateImpl) | |||
if !ok { | |||
return nil, exception.NewEngineExecutionException(seataErrors.ObjectNotExists, | |||
"Cannot find State by state name ["+stateInstance.Name()+"], may be this is a bug", nil) | |||
} | |||
if serviceTaskStateImpl.Output() != nil && len(serviceTaskStateImpl.Output()) != 0 { | |||
outputVariablesToContext, err := CreateOutputParams(p.StateMachineConfig, | |||
p.StateMachineConfig.ExpressionResolver(), serviceTaskStateImpl.AbstractTaskState, serviceOutputParams) | |||
if err != nil { | |||
return nil, exception.NewEngineExecutionException(seataErrors.ObjectNotExists, | |||
"Context variable replay failed", err) | |||
} | |||
if outputVariablesToContext != nil && len(outputVariablesToContext) != 0 { | |||
for key, value := range outputVariablesToContext { | |||
contextVariables[key] = value | |||
} | |||
} | |||
if len(stateInstance.BusinessKey()) > 0 { | |||
contextVariables[serviceTaskStateImpl.Name()+constant.VarNameBusinesskey] = stateInstance.BusinessKey() | |||
} | |||
} | |||
} | |||
} | |||
return contextVariables, nil | |||
} | |||
func (p ProcessCtrlStateMachineEngine) checkStatus(ctx context.Context, stateMachineInstance statelang.StateMachineInstance, | |||
acceptStatus []statelang.ExecutionStatus, denyStatus []statelang.ExecutionStatus, status statelang.ExecutionStatus, | |||
compenStatus statelang.ExecutionStatus, operation string) (bool, error) { | |||
if status != "" && compenStatus != "" { | |||
return false, exception.NewEngineExecutionException(seataErrors.InvalidParameter, | |||
"status and compensationStatus are not supported at the same time", nil) | |||
} | |||
if status == "" && compenStatus == "" { | |||
return false, exception.NewEngineExecutionException(seataErrors.InvalidParameter, | |||
"status and compensationStatus must input at least one", nil) | |||
} | |||
if statelang.SU == compenStatus { | |||
message := p.buildExceptionMessage(stateMachineInstance, nil, nil, "", statelang.SU, operation) | |||
return false, exception.NewEngineExecutionException(seataErrors.OperationDenied, | |||
message, nil) | |||
} | |||
if stateMachineInstance.IsRunning() && | |||
!IsTimeout(stateMachineInstance.UpdatedTime(), p.StateMachineConfig.TransOperationTimeout()) { | |||
return false, exception.NewEngineExecutionException(seataErrors.OperationDenied, | |||
"StateMachineInstance [id:"+stateMachineInstance.ID()+"] is running, operation["+operation+ | |||
"] denied", nil) | |||
} | |||
if (denyStatus == nil || len(denyStatus) == 0) && (acceptStatus == nil || len(acceptStatus) == 0) { | |||
return false, exception.NewEngineExecutionException(seataErrors.InvalidParameter, | |||
"StateMachineInstance[id:"+stateMachineInstance.ID()+ | |||
"], acceptable status and deny status must input at least one", nil) | |||
} | |||
currentStatus := compenStatus | |||
if status != "" { | |||
currentStatus = status | |||
} | |||
if denyStatus != nil && len(denyStatus) == 0 { | |||
for _, tempDenyStatus := range denyStatus { | |||
if tempDenyStatus == currentStatus { | |||
message := p.buildExceptionMessage(stateMachineInstance, acceptStatus, denyStatus, status, | |||
compenStatus, operation) | |||
return false, exception.NewEngineExecutionException(seataErrors.OperationDenied, | |||
message, nil) | |||
} | |||
} | |||
} | |||
if acceptStatus == nil || len(acceptStatus) == 0 { | |||
return true, nil | |||
} else { | |||
for _, tempStatus := range acceptStatus { | |||
if tempStatus == currentStatus { | |||
return true, nil | |||
} | |||
} | |||
} | |||
message := p.buildExceptionMessage(stateMachineInstance, acceptStatus, denyStatus, status, compenStatus, | |||
operation) | |||
return false, exception.NewEngineExecutionException(seataErrors.OperationDenied, | |||
message, nil) | |||
} | |||
func (p ProcessCtrlStateMachineEngine) getStateMachineContextVariables(ctx context.Context, | |||
stateMachineInstance statelang.StateMachineInstance) (map[string]any, error) { | |||
contextVariables := stateMachineInstance.EndParams() | |||
if contextVariables == nil || len(contextVariables) == 0 { | |||
return p.replayContextVariables(ctx, stateMachineInstance) | |||
} | |||
return contextVariables, nil | |||
} | |||
func (p ProcessCtrlStateMachineEngine) buildExceptionMessage(instance statelang.StateMachineInstance, | |||
acceptStatus []statelang.ExecutionStatus, denyStatus []statelang.ExecutionStatus, status statelang.ExecutionStatus, | |||
compenStatus statelang.ExecutionStatus, operation string) string { | |||
message := fmt.Sprintf("StateMachineInstance[id:%s]", instance.ID()) | |||
if len(acceptStatus) > 0 { | |||
message += ",acceptable status :" | |||
for _, tempStatus := range acceptStatus { | |||
message += string(tempStatus) + " " | |||
} | |||
} | |||
if len(denyStatus) > 0 { | |||
message += ",deny status:" | |||
for _, tempStatus := range denyStatus { | |||
message += string(tempStatus) + " " | |||
} | |||
} | |||
if status != "" { | |||
message += ",current status:" + string(status) | |||
} | |||
if compenStatus != "" { | |||
message += ",current compensation status:" + string(compenStatus) | |||
} | |||
message += fmt.Sprintf(",so operation [%s] denied", operation) | |||
return message | |||
} | |||
func (p ProcessCtrlStateMachineEngine) putBusinesskeyToContextariables(instance statelang.StateMachineInstance, variables map[string]any) { | |||
if instance.BusinessKey() != "" && variables[constant.VarNameBusinesskey] == "" { | |||
variables[constant.VarNameBusinesskey] = instance.BusinessKey() | |||
} | |||
} | |||
func (p ProcessCtrlStateMachineEngine) nullSafeCopy(srcMap map[string]any, destMap map[string]any) { | |||
for key, value := range srcMap { | |||
if value == nil { | |||
destMap[key] = value | |||
} | |||
} | |||
} |
@@ -0,0 +1,194 @@ | |||
package core | |||
import ( | |||
"context" | |||
"github.com/pkg/errors" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/constant" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/process_ctrl/process" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||
"github.com/seata/seata-go/pkg/util/log" | |||
) | |||
type RouterHandler interface { | |||
Route(ctx context.Context, processContext ProcessContext) error | |||
} | |||
type ProcessRouter interface { | |||
Route(ctx context.Context, processContext ProcessContext) error | |||
} | |||
type InterceptAbleStateRouter interface { | |||
StateRouter | |||
StateRouterInterceptor() []StateRouterInterceptor | |||
RegistryStateRouterInterceptor(stateRouterInterceptor StateRouterInterceptor) | |||
} | |||
type StateRouter interface { | |||
Route(ctx context.Context, processContext ProcessContext, state statelang.State) (Instruction, error) | |||
} | |||
type StateRouterInterceptor interface { | |||
PreRoute(ctx context.Context, processContext ProcessContext, state statelang.State) error | |||
PostRoute(ctx context.Context, processContext ProcessContext, instruction Instruction, err error) error | |||
Match(stateType string) bool | |||
} | |||
type DefaultRouterHandler struct { | |||
eventPublisher EventPublisher | |||
processRouters map[string]ProcessRouter | |||
} | |||
func (d *DefaultRouterHandler) Route(ctx context.Context, processContext ProcessContext) error { | |||
processType := d.matchProcessType(ctx, processContext) | |||
if processType == "" { | |||
log.Warnf("Process type not found, context= %s", processContext) | |||
return errors.New("Process type not found") | |||
} | |||
processRouter := d.processRouters[string(processType)] | |||
if processRouter == nil { | |||
log.Errorf("Cannot find process router by type %s, context = %s", processType, processContext) | |||
return errors.New("Process router not found") | |||
} | |||
instruction := processRouter.Route(ctx, processContext) | |||
if instruction == nil { | |||
log.Info("route instruction is null, process end") | |||
} else { | |||
processContext.SetInstruction(instruction) | |||
_, err := d.eventPublisher.PushEvent(ctx, processContext) | |||
if err != nil { | |||
return err | |||
} | |||
} | |||
return nil | |||
} | |||
func (d *DefaultRouterHandler) matchProcessType(ctx context.Context, processContext ProcessContext) process.ProcessType { | |||
processType, ok := processContext.GetVariable(constant.VarNameProcessType).(process.ProcessType) | |||
if !ok || processType == "" { | |||
processType = process.StateLang | |||
} | |||
return processType | |||
} | |||
func (d *DefaultRouterHandler) EventPublisher() EventPublisher { | |||
return d.eventPublisher | |||
} | |||
func (d *DefaultRouterHandler) SetEventPublisher(eventPublisher EventPublisher) { | |||
d.eventPublisher = eventPublisher | |||
} | |||
func (d *DefaultRouterHandler) ProcessRouters() map[string]ProcessRouter { | |||
return d.processRouters | |||
} | |||
func (d *DefaultRouterHandler) SetProcessRouters(processRouters map[string]ProcessRouter) { | |||
d.processRouters = processRouters | |||
} | |||
type StateMachineProcessRouter struct { | |||
stateRouters map[string]StateRouter | |||
} | |||
func (s *StateMachineProcessRouter) Route(ctx context.Context, processContext ProcessContext) (Instruction, error) { | |||
stateInstruction, ok := processContext.GetInstruction().(StateInstruction) | |||
if !ok { | |||
return nil, errors.New("instruction is not a state instruction") | |||
} | |||
var state statelang.State | |||
if stateInstruction.TemporaryState() != nil { | |||
state = stateInstruction.TemporaryState() | |||
stateInstruction.SetTemporaryState(nil) | |||
} else { | |||
stateMachineConfig, ok := processContext.GetVariable(constant.VarNameStateMachineConfig).(StateMachineConfig) | |||
if !ok { | |||
return nil, errors.New("state machine config not found") | |||
} | |||
stateMachine, err := stateMachineConfig.StateMachineRepository().GetStateMachineByNameAndTenantId(stateInstruction.StateMachineName(), | |||
stateInstruction.TenantId()) | |||
if err != nil { | |||
return nil, err | |||
} | |||
state = stateMachine.States()[stateInstruction.StateName()] | |||
} | |||
stateType := state.Type() | |||
router := s.stateRouters[stateType] | |||
var interceptors []StateRouterInterceptor | |||
if interceptAbleStateRouter, ok := router.(InterceptAbleStateRouter); ok { | |||
interceptors = interceptAbleStateRouter.StateRouterInterceptor() | |||
} | |||
var executedInterceptors []StateRouterInterceptor | |||
var exception error | |||
instruction, exception := func() (Instruction, error) { | |||
if interceptors == nil || len(executedInterceptors) == 0 { | |||
executedInterceptors = make([]StateRouterInterceptor, 0, len(interceptors)) | |||
for _, interceptor := range interceptors { | |||
executedInterceptors = append(executedInterceptors, interceptor) | |||
err := interceptor.PreRoute(ctx, processContext, state) | |||
if err != nil { | |||
return nil, err | |||
} | |||
} | |||
} | |||
instruction, err := router.Route(ctx, processContext, state) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return instruction, nil | |||
}() | |||
if interceptors == nil || len(executedInterceptors) == 0 { | |||
for i := len(executedInterceptors) - 1; i >= 0; i-- { | |||
err := executedInterceptors[i].PostRoute(ctx, processContext, instruction, exception) | |||
if err != nil { | |||
return nil, err | |||
} | |||
} | |||
// if 'Succeed' or 'Fail' State did not configured, we must end the state machine | |||
if instruction == nil && !stateInstruction.End() { | |||
err := EndStateMachine(ctx, processContext) | |||
if err != nil { | |||
return nil, err | |||
} | |||
} | |||
} | |||
return instruction, nil | |||
} | |||
func (s *StateMachineProcessRouter) InitDefaultStateRouters() { | |||
if s.stateRouters == nil || len(s.stateRouters) == 0 { | |||
s.stateRouters = make(map[string]StateRouter) | |||
taskStateRouter := &TaskStateRouter{} | |||
s.stateRouters[constant.StateTypeServiceTask] = taskStateRouter | |||
s.stateRouters[constant.StateTypeScriptTask] = taskStateRouter | |||
s.stateRouters[constant.StateTypeChoice] = taskStateRouter | |||
s.stateRouters[constant.StateTypeCompensationTrigger] = taskStateRouter | |||
s.stateRouters[constant.StateTypeSubStateMachine] = taskStateRouter | |||
s.stateRouters[constant.StateTypeCompensateSubMachine] = taskStateRouter | |||
s.stateRouters[constant.StateTypeLoopStart] = taskStateRouter | |||
endStateRouter := &EndStateRouter{} | |||
s.stateRouters[constant.StateTypeSucceed] = endStateRouter | |||
s.stateRouters[constant.StateTypeFail] = endStateRouter | |||
} | |||
} | |||
func (s *StateMachineProcessRouter) StateRouters() map[string]StateRouter { | |||
return s.stateRouters | |||
} | |||
func (s *StateMachineProcessRouter) SetStateRouters(stateRouters map[string]StateRouter) { | |||
s.stateRouters = stateRouters | |||
} |
@@ -1,8 +1,8 @@ | |||
package process_ctrl | |||
package core | |||
import ( | |||
"context" | |||
"github.com/pkg/errors" | |||
"errors" | |||
"sync" | |||
) | |||
@@ -11,20 +11,20 @@ type StateHandler interface { | |||
ProcessHandler | |||
} | |||
type StateRouter interface { | |||
State() string | |||
RouterHandler | |||
} | |||
type InterceptAbleStateHandler interface { | |||
StateHandler | |||
StateHandlerInterceptorList() []StateHandlerInterceptor | |||
RegistryStateHandlerInterceptor(stateHandlerInterceptor StateHandlerInterceptor) | |||
} | |||
type ProcessHandler interface { | |||
Process(ctx context.Context, processContext ProcessContext) error | |||
} | |||
type StateHandlerInterceptor interface { | |||
PreProcess(ctx context.Context, processContext ProcessContext) error | |||
PostProcess(ctx context.Context, processContext ProcessContext) error | |||
Match(stateType string) bool | |||
} | |||
type StateMachineProcessHandler struct { | |||
@@ -99,40 +99,3 @@ func (s *StateMachineProcessHandler) RegistryStateHandler(stateType string, stat | |||
} | |||
s.mp[stateType] = stateHandler | |||
} | |||
type StateMachineRouterHandler struct { | |||
mu sync.RWMutex | |||
mp map[string]StateRouter | |||
} | |||
func (s *StateMachineRouterHandler) Route(ctx context.Context, processContext ProcessContext) error { | |||
stateInstruction, _ := processContext.GetInstruction().(StateInstruction) | |||
state, err := stateInstruction.GetState(processContext) | |||
if err != nil { | |||
return err | |||
} | |||
stateType := state.Type() | |||
stateRouter := s.GetStateRouter(stateType) | |||
if stateRouter == nil { | |||
return errors.New("Not support [" + stateType + "] state router") | |||
} | |||
return stateRouter.Route(ctx, processContext) | |||
} | |||
func (s *StateMachineRouterHandler) GetStateRouter(stateType string) StateRouter { | |||
s.mu.RLock() | |||
defer s.mu.RUnlock() | |||
return s.mp[stateType] | |||
} | |||
func (s *StateMachineRouterHandler) RegistryStateRouter(stateType string, stateRouter StateRouter) { | |||
s.mu.Lock() | |||
defer s.mu.Unlock() | |||
if s.mp == nil { | |||
s.mp = make(map[string]StateRouter) | |||
} | |||
s.mp[stateType] = stateRouter | |||
} |
@@ -0,0 +1,151 @@ | |||
package core | |||
import ( | |||
"context" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/constant" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/engine/exception" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||
sagaState "github.com/seata/seata-go/pkg/saga/statemachine/statelang/state" | |||
seataErrors "github.com/seata/seata-go/pkg/util/errors" | |||
"github.com/seata/seata-go/pkg/util/log" | |||
) | |||
type EndStateRouter struct { | |||
} | |||
func (e EndStateRouter) Route(ctx context.Context, processContext ProcessContext, state statelang.State) (Instruction, error) { | |||
return nil, nil | |||
} | |||
type TaskStateRouter struct { | |||
} | |||
func (t TaskStateRouter) Route(ctx context.Context, processContext ProcessContext, state statelang.State) (Instruction, error) { | |||
stateInstruction, _ := processContext.GetInstruction().(StateInstruction) | |||
if stateInstruction.End() { | |||
log.Infof("StateInstruction is ended, Stop the StateMachine executing. StateMachine[%s] Current State[%s]", | |||
stateInstruction.StateMachineName(), stateInstruction.StateName()) | |||
} | |||
// check if in loop async condition | |||
isLoop, ok := processContext.GetVariable(constant.VarNameIsLoopState).(bool) | |||
if ok && isLoop { | |||
log.Infof("StateMachine[%s] Current State[%s] is in loop async condition, skip route processing.", | |||
stateInstruction.StateMachineName(), stateInstruction.StateName()) | |||
return nil, nil | |||
} | |||
// The current CompensationTriggerState can mark the compensation process is started and perform compensation | |||
// route processing. | |||
compensationTriggerState, ok := processContext.GetVariable(constant.VarNameCurrentCompensateTriggerState).(statelang.State) | |||
if ok { | |||
return t.compensateRoute(ctx, processContext, compensationTriggerState) | |||
} | |||
// There is an exception route, indicating that an exception is thrown, and the exception route is prioritized. | |||
next := processContext.GetVariable(constant.VarNameCurrentExceptionRoute).(string) | |||
if next != "" { | |||
processContext.RemoveVariable(constant.VarNameCurrentExceptionRoute) | |||
} else { | |||
next = state.Next() | |||
} | |||
// If next is empty, the state selected by the Choice state was taken. | |||
if next == "" && processContext.HasVariable(constant.VarNameCurrentChoice) { | |||
next = processContext.GetVariable(constant.VarNameCurrentChoice).(string) | |||
processContext.RemoveVariable(constant.VarNameCurrentChoice) | |||
} | |||
if next == "" { | |||
return nil, nil | |||
} | |||
stateMachine := state.StateMachine() | |||
nextState := stateMachine.State(next) | |||
if nextState == nil { | |||
return nil, exception.NewEngineExecutionException(seataErrors.ObjectNotExists, | |||
"Next state["+next+"] is not exits", nil) | |||
} | |||
stateInstruction.SetStateName(next) | |||
if nil != GetLoopConfig(ctx, processContext, nextState) { | |||
stateInstruction.SetTemporaryState(sagaState.NewLoopStartStateImpl()) | |||
} | |||
return stateInstruction, nil | |||
} | |||
func (t *TaskStateRouter) compensateRoute(ctx context.Context, processContext ProcessContext, | |||
compensationTriggerState statelang.State) (Instruction, error) { | |||
//If there is already a compensation state that has been executed, | |||
// it is judged whether it is wrong or unsuccessful, | |||
// and the compensation process is interrupted. | |||
isFirstCompensationStateStart := processContext.GetVariable(constant.VarNameFirstCompensationStateStarted).(bool) | |||
if isFirstCompensationStateStart { | |||
exception := processContext.GetVariable(constant.VarNameCurrentException).(error) | |||
if exception != nil { | |||
return nil, EndStateMachine(ctx, processContext) | |||
} | |||
stateInstance := processContext.GetVariable(constant.VarNameStateInst).(statelang.StateInstance) | |||
if stateInstance != nil && statelang.SU != stateInstance.Status() { | |||
return nil, EndStateMachine(ctx, processContext) | |||
} | |||
} | |||
stateStackToBeCompensated := GetCurrentCompensationHolder(ctx, processContext, true).StateStackNeedCompensation() | |||
if stateStackToBeCompensated != nil { | |||
stateToBeCompensated := stateStackToBeCompensated.Pop().(statelang.StateInstance) | |||
stateMachine := processContext.GetVariable(constant.VarNameStateMachine).(statelang.StateMachine) | |||
state := stateMachine.State(GetOriginStateName(stateToBeCompensated)) | |||
if taskState, ok := state.(sagaState.AbstractTaskState); ok { | |||
instruction := processContext.GetInstruction().(StateInstruction) | |||
var compensateState statelang.State | |||
compensateStateName := taskState.CompensateState() | |||
if len(compensateStateName) != 0 { | |||
compensateState = stateMachine.State(compensateStateName) | |||
} | |||
if subStateMachine, ok := state.(sagaState.SubStateMachine); compensateState == nil && ok { | |||
compensateState = subStateMachine.CompensateStateImpl() | |||
instruction.SetTemporaryState(compensateState) | |||
} | |||
if compensateState == nil { | |||
return nil, EndStateMachine(ctx, processContext) | |||
} | |||
instruction.SetStateName(compensateState.Name()) | |||
GetCurrentCompensationHolder(ctx, processContext, true).AddToBeCompensatedState(compensateState.Name(), | |||
stateToBeCompensated) | |||
hierarchicalProcessContext := processContext.(HierarchicalProcessContext) | |||
hierarchicalProcessContext.SetVariableLocally(constant.VarNameFirstCompensationStateStarted, true) | |||
if _, ok := compensateState.(sagaState.CompensateSubStateMachineState); ok { | |||
hierarchicalProcessContext = processContext.(HierarchicalProcessContext) | |||
hierarchicalProcessContext.SetVariableLocally( | |||
compensateState.Name()+constant.VarNameSubMachineParentId, | |||
GenerateParentId(stateToBeCompensated)) | |||
} | |||
return instruction, nil | |||
} | |||
} | |||
processContext.RemoveVariable(constant.VarNameCurrentCompensateTriggerState) | |||
compensationTriggerStateNext := compensationTriggerState.Next() | |||
if compensationTriggerStateNext == "" { | |||
return nil, EndStateMachine(ctx, processContext) | |||
} | |||
instruction := processContext.GetInstruction().(StateInstruction) | |||
instruction.SetStateName(compensationTriggerStateNext) | |||
return instruction, nil | |||
} |
@@ -1,22 +1,20 @@ | |||
package engine | |||
package core | |||
import ( | |||
"github.com/seata/seata-go/pkg/saga/statemachine/engine/events" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/engine/expr" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/engine/invoker" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/engine/sequence" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/engine/status_decision" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/engine/store" | |||
"sync" | |||
) | |||
type StateMachineConfig interface { | |||
StateLogRepository() store.StateLogRepository | |||
StateLogRepository() StateLogRepository | |||
StateMachineRepository() store.StateMachineRepository | |||
StateMachineRepository() StateMachineRepository | |||
StateLogStore() store.StateLogStore | |||
StateLogStore() StateLogStore | |||
StateLangStore() store.StateLangStore | |||
StateLangStore() StateLangStore | |||
ExpressionFactoryManager() expr.ExpressionFactoryManager | |||
@@ -24,11 +22,11 @@ type StateMachineConfig interface { | |||
SeqGenerator() sequence.SeqGenerator | |||
StatusDecisionStrategy() status_decision.StatusDecisionStrategy | |||
StatusDecisionStrategy() StatusDecisionStrategy | |||
EventPublisher() events.EventPublisher | |||
EventPublisher() EventPublisher | |||
AsyncEventPublisher() events.EventPublisher | |||
AsyncEventPublisher() EventPublisher | |||
ServiceInvokerManager() invoker.ServiceInvokerManager | |||
@@ -41,4 +39,6 @@ type StateMachineConfig interface { | |||
TransOperationTimeout() int | |||
ServiceInvokeTimeout() int | |||
ComponentLock() *sync.Mutex | |||
} |
@@ -0,0 +1,16 @@ | |||
package core | |||
import ( | |||
"context" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||
) | |||
type StateMachineEngine interface { | |||
Start(ctx context.Context, stateMachineName string, tenantId string, startParams map[string]interface{}) (statelang.StateMachineInstance, error) | |||
Compensate(ctx context.Context, stateMachineInstId string, replaceParams map[string]any) (statelang.StateMachineInstance, error) | |||
} | |||
type CallBack interface { | |||
OnFinished(ctx context.Context, context ProcessContext, stateMachineInstance statelang.StateMachineInstance) | |||
OnError(ctx context.Context, context ProcessContext, stateMachineInstance statelang.StateMachineInstance, err error) | |||
} |
@@ -0,0 +1,15 @@ | |||
package core | |||
import ( | |||
"context" | |||
"testing" | |||
) | |||
func TestEngine(t *testing.T) { | |||
} | |||
func TestSimpleStateMachine(t *testing.T) { | |||
engine := NewProcessCtrlStateMachineEngine() | |||
engine.Start(context.Background(), "simpleStateMachine", "tenantId", nil) | |||
} |
@@ -1,8 +1,7 @@ | |||
package store | |||
package core | |||
import ( | |||
"context" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/engine/process_ctrl" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||
"io" | |||
) | |||
@@ -20,15 +19,15 @@ type StateLogRepository interface { | |||
} | |||
type StateLogStore interface { | |||
RecordStateMachineStarted(ctx context.Context, machineInstance statelang.StateMachineInstance, context process_ctrl.ProcessContext) error | |||
RecordStateMachineStarted(ctx context.Context, machineInstance statelang.StateMachineInstance, context ProcessContext) error | |||
RecordStateMachineFinished(ctx context.Context, machineInstance statelang.StateMachineInstance, context process_ctrl.ProcessContext) error | |||
RecordStateMachineFinished(ctx context.Context, machineInstance statelang.StateMachineInstance, context ProcessContext) error | |||
RecordStateMachineRestarted(ctx context.Context, machineInstance statelang.StateMachineInstance, context process_ctrl.ProcessContext) error | |||
RecordStateMachineRestarted(ctx context.Context, machineInstance statelang.StateMachineInstance, context ProcessContext) error | |||
RecordStateStarted(ctx context.Context, stateInstance statelang.StateInstance, context process_ctrl.ProcessContext) error | |||
RecordStateStarted(ctx context.Context, stateInstance statelang.StateInstance, context ProcessContext) error | |||
RecordStateFinished(ctx context.Context, stateInstance statelang.StateInstance, context process_ctrl.ProcessContext) error | |||
RecordStateFinished(ctx context.Context, stateInstance statelang.StateInstance, context ProcessContext) error | |||
GetStateMachineInstance(stateMachineInstanceId string) (statelang.StateMachineInstance, error) | |||
@@ -44,6 +43,8 @@ type StateLogStore interface { | |||
type StateMachineRepository interface { | |||
GetStateMachineById(stateMachineId string) (statelang.StateMachine, error) | |||
GetStateMachineByNameAndTenantId(stateMachineName string, tenantId string) (statelang.StateMachine, error) | |||
GetLastVersionStateMachine(stateMachineName string, tenantId string) (statelang.StateMachine, error) | |||
RegistryStateMachine(statelang.StateMachine) error |
@@ -0,0 +1,188 @@ | |||
package core | |||
import ( | |||
"context" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/constant" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||
"github.com/seata/seata-go/pkg/util/log" | |||
) | |||
type StatusDecisionStrategy interface { | |||
// DecideOnEndState Determine state machine execution status when executing to EndState | |||
DecideOnEndState(ctx context.Context, processContext ProcessContext, | |||
stateMachineInstance statelang.StateMachineInstance, exp error) error | |||
// DecideOnTaskStateFail Determine state machine execution status when executing TaskState error | |||
DecideOnTaskStateFail(ctx context.Context, processContext ProcessContext, | |||
stateMachineInstance statelang.StateMachineInstance, exp error) error | |||
// DecideMachineForwardExecutionStatus Determine the forward execution state of the state machine | |||
DecideMachineForwardExecutionStatus(ctx context.Context, | |||
stateMachineInstance statelang.StateMachineInstance, exp error, specialPolicy bool) error | |||
} | |||
type DefaultStatusDecisionStrategy struct { | |||
} | |||
func NewDefaultStatusDecisionStrategy() *DefaultStatusDecisionStrategy { | |||
return &DefaultStatusDecisionStrategy{} | |||
} | |||
func (d DefaultStatusDecisionStrategy) DecideOnEndState(ctx context.Context, processContext ProcessContext, | |||
stateMachineInstance statelang.StateMachineInstance, exp error) error { | |||
if statelang.RU == stateMachineInstance.CompensationStatus() { | |||
compensationHolder := GetCurrentCompensationHolder(ctx, processContext, true) | |||
if err := decideMachineCompensateStatus(ctx, stateMachineInstance, compensationHolder); err != nil { | |||
return err | |||
} | |||
} else { | |||
failEndStateFlag, ok := processContext.GetVariable(constant.VarNameFailEndStateFlag).(bool) | |||
if !ok { | |||
failEndStateFlag = false | |||
} | |||
if _, err := decideMachineForwardExecutionStatus(ctx, stateMachineInstance, exp, failEndStateFlag); err != nil { | |||
return err | |||
} | |||
} | |||
if stateMachineInstance.CompensationStatus() != "" && constant.OperationNameForward == | |||
processContext.GetVariable(constant.VarNameOperationName).(string) && statelang.SU == stateMachineInstance.Status() { | |||
stateMachineInstance.SetCompensationStatus(statelang.FA) | |||
} | |||
log.Debugf("StateMachine Instance[id:%s,name:%s] execute finish with status[%s], compensation status [%s].", | |||
stateMachineInstance.ID(), stateMachineInstance.StateMachine().Name(), | |||
stateMachineInstance.Status(), stateMachineInstance.CompensationStatus()) | |||
return nil | |||
} | |||
func decideMachineCompensateStatus(ctx context.Context, stateMachineInstance statelang.StateMachineInstance, compensationHolder *CompensationHolder) error { | |||
if stateMachineInstance.Status() == "" || statelang.RU == stateMachineInstance.Status() { | |||
stateMachineInstance.SetStatus(statelang.UN) | |||
} | |||
if !compensationHolder.StateStackNeedCompensation().Empty() { | |||
hasCompensateSUorUN := false | |||
compensationHolder.StatesForCompensation().Range( | |||
func(key, value any) bool { | |||
stateInstance, ok := value.(statelang.StateInstance) | |||
if !ok { | |||
return false | |||
} | |||
if statelang.UN == stateInstance.Status() || statelang.SU == stateInstance.Status() { | |||
hasCompensateSUorUN = true | |||
return true | |||
} | |||
return false | |||
}) | |||
if hasCompensateSUorUN { | |||
stateMachineInstance.SetCompensationStatus(statelang.UN) | |||
} else { | |||
stateMachineInstance.SetCompensationStatus(statelang.FA) | |||
} | |||
} else { | |||
hasCompensateError := false | |||
compensationHolder.StatesForCompensation().Range( | |||
func(key, value any) bool { | |||
stateInstance, ok := value.(statelang.StateInstance) | |||
if !ok { | |||
return false | |||
} | |||
if statelang.SU != stateInstance.Status() { | |||
hasCompensateError = true | |||
return true | |||
} | |||
return false | |||
}) | |||
if hasCompensateError { | |||
stateMachineInstance.SetCompensationStatus(statelang.UN) | |||
} else { | |||
stateMachineInstance.SetCompensationStatus(statelang.SU) | |||
} | |||
} | |||
return nil | |||
} | |||
func decideMachineForwardExecutionStatus(ctx context.Context, stateMachineInstance statelang.StateMachineInstance, exp error, specialPolicy bool) (bool, error) { | |||
result := false | |||
if stateMachineInstance.Status() == "" || statelang.RU == stateMachineInstance.Status() { | |||
result = true | |||
stateList := stateMachineInstance.StateList() | |||
setMachineStatusBasedOnStateListAndException(stateMachineInstance, stateList, exp) | |||
if specialPolicy && statelang.SU == stateMachineInstance.Status() { | |||
for _, stateInstance := range stateMachineInstance.StateList() { | |||
if !stateInstance.IsIgnoreStatus() && (stateInstance.IsForUpdate() || stateInstance.IsForCompensation()) { | |||
stateMachineInstance.SetStatus(statelang.UN) | |||
break | |||
} | |||
} | |||
if statelang.SU == stateMachineInstance.Status() { | |||
stateMachineInstance.SetStatus(statelang.FA) | |||
} | |||
} | |||
} | |||
return result, nil | |||
} | |||
func setMachineStatusBasedOnStateListAndException(stateMachineInstance statelang.StateMachineInstance, | |||
stateList []statelang.StateInstance, exp error) { | |||
hasSetStatus := false | |||
hasSuccessUpdateService := false | |||
if stateList != nil && len(stateList) > 0 { | |||
hasUnsuccessService := false | |||
for i := len(stateList) - 1; i >= 0; i-- { | |||
stateInstance := stateList[i] | |||
if stateInstance.IsIgnoreStatus() || stateInstance.IsForCompensation() { | |||
continue | |||
} | |||
if statelang.UN == stateInstance.Status() { | |||
stateMachineInstance.SetStatus(statelang.UN) | |||
hasSetStatus = true | |||
} else if statelang.SU == stateInstance.Status() { | |||
if constant.StateTypeServiceTask == stateInstance.Type() { | |||
if stateInstance.IsForUpdate() && !stateInstance.IsForCompensation() { | |||
hasSuccessUpdateService = true | |||
} | |||
} | |||
} else if statelang.SK == stateInstance.Status() { | |||
// ignore | |||
} else { | |||
hasUnsuccessService = true | |||
} | |||
} | |||
if !hasSetStatus && hasUnsuccessService { | |||
if hasSuccessUpdateService { | |||
stateMachineInstance.SetStatus(statelang.UN) | |||
} else { | |||
stateMachineInstance.SetStatus(statelang.FA) | |||
} | |||
hasSetStatus = true | |||
} | |||
} | |||
if !hasSetStatus { | |||
setMachineStatusBasedOnException(stateMachineInstance, exp, hasSuccessUpdateService) | |||
} | |||
} | |||
func setMachineStatusBasedOnException(stateMachineInstance statelang.StateMachineInstance, exp error, hasSuccessUpdateService bool) { | |||
//TODO implement me | |||
panic("implement me") | |||
} | |||
func (d DefaultStatusDecisionStrategy) DecideOnTaskStateFail(ctx context.Context, processContext ProcessContext, | |||
stateMachineInstance statelang.StateMachineInstance, exp error) error { | |||
//TODO implement me | |||
panic("implement me") | |||
} | |||
func (d DefaultStatusDecisionStrategy) DecideMachineForwardExecutionStatus(ctx context.Context, | |||
stateMachineInstance statelang.StateMachineInstance, exp error, specialPolicy bool) error { | |||
//TODO implement me | |||
panic("implement me") | |||
} |
@@ -1,22 +1,22 @@ | |||
package engine | |||
package core | |||
import ( | |||
"github.com/seata/seata-go/pkg/saga/statemachine/constant" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/engine/process_ctrl" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/process_ctrl/process" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||
) | |||
// ProcessContextBuilder process_ctrl builder | |||
type ProcessContextBuilder struct { | |||
processContext process_ctrl.ProcessContext | |||
processContext ProcessContext | |||
} | |||
func NewProcessContextBuilder() *ProcessContextBuilder { | |||
processContextImpl := process_ctrl.NewProcessContextImpl() | |||
processContextImpl := NewProcessContextImpl() | |||
return &ProcessContextBuilder{processContextImpl} | |||
} | |||
func (p *ProcessContextBuilder) WithProcessType(processType process_ctrl.ProcessType) *ProcessContextBuilder { | |||
func (p *ProcessContextBuilder) WithProcessType(processType process.ProcessType) *ProcessContextBuilder { | |||
p.processContext.SetVariable(constant.VarNameProcessType, processType) | |||
return p | |||
} | |||
@@ -34,7 +34,7 @@ func (p *ProcessContextBuilder) WithAsyncCallback(callBack CallBack) *ProcessCon | |||
return p | |||
} | |||
func (p *ProcessContextBuilder) WithInstruction(instruction process_ctrl.Instruction) *ProcessContextBuilder { | |||
func (p *ProcessContextBuilder) WithInstruction(instruction Instruction) *ProcessContextBuilder { | |||
if instruction != nil { | |||
p.processContext.SetInstruction(instruction) | |||
} | |||
@@ -81,6 +81,6 @@ func (p *ProcessContextBuilder) WithIsAsyncExecution(async bool) *ProcessContext | |||
return p | |||
} | |||
func (p *ProcessContextBuilder) Build() process_ctrl.ProcessContext { | |||
func (p *ProcessContextBuilder) Build() ProcessContext { | |||
return p.processContext | |||
} |
@@ -1,123 +0,0 @@ | |||
package engine | |||
import ( | |||
"github.com/seata/seata-go/pkg/saga/statemachine/engine/events" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/engine/expr" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/engine/invoker" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/engine/sequence" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/engine/status_decision" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/engine/store" | |||
) | |||
const ( | |||
DefaultTransOperTimeout = 60000 * 30 | |||
DefaultServiceInvokeTimeout = 60000 * 5 | |||
) | |||
type DefaultStateMachineConfig struct { | |||
// Configuration | |||
transOperationTimeout int | |||
serviceInvokeTimeout int | |||
charset string | |||
defaultTenantId string | |||
// Components | |||
// Store related components | |||
stateLogRepository store.StateLogRepository | |||
stateLogStore store.StateLogStore | |||
stateLangStore store.StateLangStore | |||
stateMachineRepository store.StateMachineRepository | |||
// Expression related components | |||
expressionFactoryManager expr.ExpressionFactoryManager | |||
expressionResolver expr.ExpressionResolver | |||
// Invoker related components | |||
serviceInvokerManager invoker.ServiceInvokerManager | |||
scriptInvokerManager invoker.ScriptInvokerManager | |||
// Other components | |||
statusDecisionStrategy status_decision.StatusDecisionStrategy | |||
seqGenerator sequence.SeqGenerator | |||
} | |||
func (c *DefaultStateMachineConfig) StateLogRepository() store.StateLogRepository { | |||
return c.stateLogRepository | |||
} | |||
func (c *DefaultStateMachineConfig) StateMachineRepository() store.StateMachineRepository { | |||
return c.stateMachineRepository | |||
} | |||
func (c *DefaultStateMachineConfig) StateLogStore() store.StateLogStore { | |||
return c.stateLogStore | |||
} | |||
func (c *DefaultStateMachineConfig) StateLangStore() store.StateLangStore { | |||
return c.stateLangStore | |||
} | |||
func (c *DefaultStateMachineConfig) ExpressionFactoryManager() expr.ExpressionFactoryManager { | |||
return c.expressionFactoryManager | |||
} | |||
func (c *DefaultStateMachineConfig) ExpressionResolver() expr.ExpressionResolver { | |||
return c.expressionResolver | |||
} | |||
func (c *DefaultStateMachineConfig) SeqGenerator() sequence.SeqGenerator { | |||
return c.seqGenerator | |||
} | |||
func (c *DefaultStateMachineConfig) StatusDecisionStrategy() status_decision.StatusDecisionStrategy { | |||
return c.statusDecisionStrategy | |||
} | |||
func (c *DefaultStateMachineConfig) EventPublisher() events.EventPublisher { | |||
//TODO implement me | |||
panic("implement me") | |||
} | |||
func (c *DefaultStateMachineConfig) AsyncEventPublisher() events.EventPublisher { | |||
//TODO implement me | |||
panic("implement me") | |||
} | |||
func (c *DefaultStateMachineConfig) ServiceInvokerManager() invoker.ServiceInvokerManager { | |||
return c.serviceInvokerManager | |||
} | |||
func (c *DefaultStateMachineConfig) ScriptInvokerManager() invoker.ScriptInvokerManager { | |||
return c.scriptInvokerManager | |||
} | |||
func (c *DefaultStateMachineConfig) CharSet() string { | |||
return c.charset | |||
} | |||
func (c *DefaultStateMachineConfig) SetCharSet(charset string) { | |||
c.charset = charset | |||
} | |||
func (c *DefaultStateMachineConfig) DefaultTenantId() string { | |||
return c.defaultTenantId | |||
} | |||
func (c *DefaultStateMachineConfig) TransOperationTimeout() int { | |||
return c.transOperationTimeout | |||
} | |||
func (c *DefaultStateMachineConfig) ServiceInvokeTimeout() int { | |||
return c.serviceInvokeTimeout | |||
} | |||
func NewDefaultStateMachineConfig() *DefaultStateMachineConfig { | |||
c := &DefaultStateMachineConfig{ | |||
transOperationTimeout: DefaultTransOperTimeout, | |||
serviceInvokeTimeout: DefaultServiceInvokeTimeout, | |||
charset: "UTF-8", | |||
defaultTenantId: "000001", | |||
} | |||
return c | |||
} |
@@ -1,49 +0,0 @@ | |||
package events | |||
import ( | |||
"context" | |||
) | |||
type EventBus interface { | |||
Offer(ctx context.Context, event Event) (bool, error) | |||
RegisterEventConsumer(consumer EventConsumer) | |||
} | |||
type BaseEventBus struct { | |||
eventConsumerList []EventConsumer | |||
} | |||
func (b *BaseEventBus) RegisterEventConsumer(consumer EventConsumer) { | |||
if b.eventConsumerList == nil { | |||
b.eventConsumerList = make([]EventConsumer, 0) | |||
} | |||
b.eventConsumerList = append(b.eventConsumerList, consumer) | |||
} | |||
func (b *BaseEventBus) GetEventConsumerList(event Event) []EventConsumer { | |||
var acceptedConsumerList = make([]EventConsumer, 0) | |||
for i := range b.eventConsumerList { | |||
eventConsumer := b.eventConsumerList[i] | |||
if eventConsumer.Accept(event) { | |||
acceptedConsumerList = append(acceptedConsumerList, eventConsumer) | |||
} | |||
} | |||
return acceptedConsumerList | |||
} | |||
type DirectEventBus struct { | |||
BaseEventBus | |||
} | |||
func (d DirectEventBus) Offer(ctx context.Context, event Event) (bool, error) { | |||
eventConsumerList := d.GetEventConsumerList(event) | |||
if len(eventConsumerList) == 0 { | |||
//TODO logger | |||
return false, nil | |||
} | |||
//processContext, _ := event.(process_ctrl.ProcessContext) | |||
return true, nil | |||
} |
@@ -0,0 +1,54 @@ | |||
package exception | |||
import "github.com/seata/seata-go/pkg/util/errors" | |||
type EngineExecutionException struct { | |||
errors.SeataError | |||
stateName string | |||
stateMachineName string | |||
stateMachineInstanceId string | |||
stateInstanceId string | |||
} | |||
func NewEngineExecutionException(code errors.TransactionErrorCode, msg string, parent error) *EngineExecutionException { | |||
seataError := errors.New(code, msg, parent) | |||
return &EngineExecutionException{ | |||
SeataError: *seataError, | |||
} | |||
} | |||
func (e *EngineExecutionException) StateName() string { | |||
return e.stateName | |||
} | |||
func (e *EngineExecutionException) SetStateName(stateName string) { | |||
e.stateName = stateName | |||
} | |||
func (e *EngineExecutionException) StateMachineName() string { | |||
return e.stateMachineName | |||
} | |||
func (e *EngineExecutionException) SetStateMachineName(stateMachineName string) { | |||
e.stateMachineName = stateMachineName | |||
} | |||
func (e *EngineExecutionException) StateMachineInstanceId() string { | |||
return e.stateMachineInstanceId | |||
} | |||
func (e *EngineExecutionException) SetStateMachineInstanceId(stateMachineInstanceId string) { | |||
e.stateMachineInstanceId = stateMachineInstanceId | |||
} | |||
func (e *EngineExecutionException) StateInstanceId() string { | |||
return e.stateInstanceId | |||
} | |||
func (e *EngineExecutionException) SetStateInstanceId(stateInstanceId string) { | |||
e.stateInstanceId = stateInstanceId | |||
} | |||
type ForwardInvalidException struct { | |||
EngineExecutionException | |||
} |
@@ -1,10 +1,93 @@ | |||
package expr | |||
import ( | |||
"github.com/seata/seata-go/pkg/saga/statemachine/engine/sequence" | |||
"strings" | |||
) | |||
const DefaultExpressionType string = "Default" | |||
type ExpressionResolver interface { | |||
Expression(expressionStr string) Expression | |||
ExpressionFactoryManager() ExpressionFactoryManager | |||
SetExpressionFactoryManager(expressionFactoryManager ExpressionFactoryManager) | |||
} | |||
type Expression interface { | |||
Value(elContext any) any | |||
SetValue(value any, elContext any) | |||
ExpressionString() string | |||
} | |||
type ExpressionFactory interface { | |||
CreateExpression(expression string) Expression | |||
} | |||
type ExpressionFactoryManager struct { | |||
expressionFactoryMap map[string]ExpressionFactory | |||
} | |||
func NewExpressionFactoryManager() *ExpressionFactoryManager { | |||
return &ExpressionFactoryManager{ | |||
expressionFactoryMap: make(map[string]ExpressionFactory), | |||
} | |||
} | |||
func (e *ExpressionFactoryManager) GetExpressionFactory(expressionType string) ExpressionFactory { | |||
if strings.TrimSpace(expressionType) == "" { | |||
expressionType = DefaultExpressionType | |||
} | |||
return e.expressionFactoryMap[expressionType] | |||
} | |||
func (e *ExpressionFactoryManager) SetExpressionFactoryMap(expressionFactoryMap map[string]ExpressionFactory) { | |||
for k, v := range expressionFactoryMap { | |||
e.expressionFactoryMap[k] = v | |||
} | |||
} | |||
func (e *ExpressionFactoryManager) PutExpressionFactory(expressionType string, factory ExpressionFactory) { | |||
e.expressionFactoryMap[expressionType] = factory | |||
} | |||
type SequenceExpression struct { | |||
seqGenerator sequence.SeqGenerator | |||
entity string | |||
rule string | |||
} | |||
func (s *SequenceExpression) SeqGenerator() sequence.SeqGenerator { | |||
return s.seqGenerator | |||
} | |||
func (s *SequenceExpression) SetSeqGenerator(seqGenerator sequence.SeqGenerator) { | |||
s.seqGenerator = seqGenerator | |||
} | |||
func (s *SequenceExpression) Entity() string { | |||
return s.entity | |||
} | |||
func (s *SequenceExpression) SetEntity(entity string) { | |||
s.entity = entity | |||
} | |||
func (s *SequenceExpression) Rule() string { | |||
return s.rule | |||
} | |||
func (s *SequenceExpression) SetRule(rule string) { | |||
s.rule = rule | |||
} | |||
func (s SequenceExpression) Value(elContext any) any { | |||
return s.seqGenerator.GenerateId(s.entity, s.rule) | |||
} | |||
func (s SequenceExpression) SetValue(value any, elContext any) { | |||
} | |||
func (s SequenceExpression) ExpressionString() string { | |||
return s.entity + "|" + s.rule | |||
} |
@@ -1,24 +0,0 @@ | |||
package process_ctrl | |||
import ( | |||
"github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||
) | |||
type Instruction interface { | |||
} | |||
type StateInstruction struct { | |||
StateName string | |||
StateMachineName string | |||
TenantId string | |||
End bool | |||
} | |||
func (s StateInstruction) GetState(context ProcessContext) (statelang.State, error) { | |||
//TODO implement me | |||
panic("implement me") | |||
} | |||
func NewStateInstruction(stateMachineName string, tenantId string) *StateInstruction { | |||
return &StateInstruction{StateMachineName: stateMachineName, TenantId: tenantId} | |||
} |
@@ -1,125 +0,0 @@ | |||
package engine | |||
import ( | |||
"context" | |||
"time" | |||
"github.com/pkg/errors" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/constant" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/engine/events" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/engine/process_ctrl" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||
) | |||
type ProcessCtrlStateMachineEngine struct { | |||
StateMachineConfig StateMachineConfig | |||
} | |||
func (p ProcessCtrlStateMachineEngine) Start(ctx context.Context, stateMachineName string, tenantId string, startParams map[string]interface{}) (statelang.StateMachineInstance, error) { | |||
return p.startInternal(ctx, stateMachineName, tenantId, "", startParams, false, nil) | |||
} | |||
func (p ProcessCtrlStateMachineEngine) startInternal(ctx context.Context, stateMachineName string, tenantId string, businessKey string, startParams map[string]interface{}, async bool, callback CallBack) (statelang.StateMachineInstance, error) { | |||
if tenantId == "" { | |||
tenantId = p.StateMachineConfig.DefaultTenantId() | |||
} | |||
stateMachineInstance, err := p.createMachineInstance(stateMachineName, tenantId, businessKey, startParams) | |||
if err != nil { | |||
return nil, err | |||
} | |||
// Build the process_ctrl context. | |||
processContextBuilder := NewProcessContextBuilder(). | |||
WithProcessType(process_ctrl.StateLang). | |||
WithOperationName(constant.OperationNameStart). | |||
WithAsyncCallback(callback). | |||
WithInstruction(process_ctrl.NewStateInstruction(stateMachineName, tenantId)). | |||
WithStateMachineInstance(stateMachineInstance). | |||
WithStateMachineConfig(p.StateMachineConfig). | |||
WithStateMachineEngine(p). | |||
WithIsAsyncExecution(async) | |||
contextMap := p.copyMap(startParams) | |||
stateMachineInstance.SetContext(contextMap) | |||
processContext := processContextBuilder.WithStateMachineContextVariables(contextMap).Build() | |||
if stateMachineInstance.StateMachine().IsPersist() && p.StateMachineConfig.StateLogStore() != nil { | |||
err := p.StateMachineConfig.StateLogStore().RecordStateMachineStarted(ctx, stateMachineInstance, processContext) | |||
if err != nil { | |||
return nil, err | |||
} | |||
} | |||
if stateMachineInstance.ID() == "" { | |||
stateMachineInstance.SetID(p.StateMachineConfig.SeqGenerator().GenerateId(constant.SeqEntityStateMachineInst, "")) | |||
} | |||
var eventPublisher events.EventPublisher | |||
if async { | |||
eventPublisher = p.StateMachineConfig.AsyncEventPublisher() | |||
} else { | |||
eventPublisher = p.StateMachineConfig.EventPublisher() | |||
} | |||
_, err = eventPublisher.PushEvent(ctx, processContext) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return stateMachineInstance, nil | |||
} | |||
// copyMap not deep copy, so best practice: Don’t pass by reference | |||
func (p ProcessCtrlStateMachineEngine) copyMap(startParams map[string]interface{}) map[string]interface{} { | |||
copyMap := make(map[string]interface{}, len(startParams)) | |||
for k, v := range startParams { | |||
copyMap[k] = v | |||
} | |||
return copyMap | |||
} | |||
func (p ProcessCtrlStateMachineEngine) createMachineInstance(stateMachineName string, tenantId string, businessKey string, startParams map[string]interface{}) (statelang.StateMachineInstance, error) { | |||
stateMachine, err := p.StateMachineConfig.StateMachineRepository().GetLastVersionStateMachine(stateMachineName, tenantId) | |||
if err != nil { | |||
return nil, err | |||
} | |||
if stateMachine == nil { | |||
return nil, errors.New("StateMachine [" + stateMachineName + "] is not exists") | |||
} | |||
stateMachineInstance := statelang.NewStateMachineInstanceImpl() | |||
stateMachineInstance.SetStateMachine(stateMachine) | |||
stateMachineInstance.SetTenantID(tenantId) | |||
stateMachineInstance.SetBusinessKey(businessKey) | |||
stateMachineInstance.SetStartParams(startParams) | |||
if startParams != nil { | |||
if businessKey != "" { | |||
startParams[constant.VarNameBusinesskey] = businessKey | |||
} | |||
if startParams[constant.VarNameParentId] != nil { | |||
parentId, ok := startParams[constant.VarNameParentId].(string) | |||
if !ok { | |||
} | |||
stateMachineInstance.SetParentID(parentId) | |||
delete(startParams, constant.VarNameParentId) | |||
} | |||
} | |||
stateMachineInstance.SetStatus(statelang.RU) | |||
stateMachineInstance.SetRunning(true) | |||
now := time.Now() | |||
stateMachineInstance.SetStartedTime(now) | |||
stateMachineInstance.SetUpdatedTime(now) | |||
return stateMachineInstance, nil | |||
} | |||
func NewProcessCtrlStateMachineEngine(stateMachineConfig StateMachineConfig) *ProcessCtrlStateMachineEngine { | |||
return &ProcessCtrlStateMachineEngine{StateMachineConfig: stateMachineConfig} | |||
} |
@@ -1,16 +0,0 @@ | |||
package engine | |||
import ( | |||
"context" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/engine/process_ctrl" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||
) | |||
type StateMachineEngine interface { | |||
Start(ctx context.Context, stateMachineName string, tenantId string, startParams map[string]interface{}) (statelang.StateMachineInstance, error) | |||
} | |||
type CallBack interface { | |||
OnFinished(ctx context.Context, context process_ctrl.ProcessContext, stateMachineInstance statelang.StateMachineInstance) | |||
OnError(ctx context.Context, context process_ctrl.ProcessContext, stateMachineInstance statelang.StateMachineInstance, err error) | |||
} |
@@ -1,7 +0,0 @@ | |||
package engine | |||
import "testing" | |||
func TestEngine(t *testing.T) { | |||
} |
@@ -1,4 +0,0 @@ | |||
package status_decision | |||
type StatusDecisionStrategy interface { | |||
} |
@@ -0,0 +1,174 @@ | |||
package handlers | |||
import ( | |||
"context" | |||
"errors" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/constant" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/engine/core" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/engine/exception" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/statelang/state" | |||
seataErrors "github.com/seata/seata-go/pkg/util/errors" | |||
"github.com/seata/seata-go/pkg/util/log" | |||
) | |||
type ServiceTaskStateHandler struct { | |||
interceptors []core.StateHandlerInterceptor | |||
} | |||
func NewServiceTaskStateHandler() *ServiceTaskStateHandler { | |||
return &ServiceTaskStateHandler{} | |||
} | |||
func (s *ServiceTaskStateHandler) State() string { | |||
return constant.StateTypeServiceTask | |||
} | |||
func (s *ServiceTaskStateHandler) Process(ctx context.Context, processContext core.ProcessContext) error { | |||
stateInstruction, ok := processContext.GetInstruction().(core.StateInstruction) | |||
if !ok { | |||
return errors.New("invalid state instruction from processContext") | |||
} | |||
stateInterface, err := stateInstruction.GetState(processContext) | |||
if err != nil { | |||
return err | |||
} | |||
serviceTaskStateImpl, ok := stateInterface.(*state.ServiceTaskStateImpl) | |||
serviceName := serviceTaskStateImpl.ServiceName() | |||
methodName := serviceTaskStateImpl.ServiceMethod() | |||
stateInstance, ok := processContext.GetVariable(constant.VarNameStateInst).(statelang.StateInstance) | |||
if !ok { | |||
return errors.New("invalid state instance type from processContext") | |||
} | |||
// invoke service task and record | |||
var result any | |||
var resultErr error | |||
handleResultErr := func(err error) { | |||
log.Error("<<<<<<<<<<<<<<<<<<<<<< State[%s], ServiceName[%s], Method[%s] Execute failed.", | |||
serviceTaskStateImpl.Name(), serviceName, methodName, err) | |||
hierarchicalProcessContext, ok := processContext.(core.HierarchicalProcessContext) | |||
if !ok { | |||
return | |||
} | |||
hierarchicalProcessContext.SetVariable(constant.VarNameCurrentException, err) | |||
core.HandleException(processContext, serviceTaskStateImpl.AbstractTaskState, err) | |||
} | |||
input, ok := processContext.GetVariable(constant.VarNameInputParams).([]any) | |||
if !ok { | |||
handleResultErr(errors.New("invalid input params type from processContext")) | |||
return nil | |||
} | |||
stateInstance.SetStatus(statelang.RU) | |||
log.Debugf(">>>>>>>>>>>>>>>>>>>>>> Start to execute State[%s], ServiceName[%s], Method[%s], Input:%s", | |||
serviceTaskStateImpl.Name(), serviceName, methodName, input) | |||
if _, ok := stateInterface.(state.CompensateSubStateMachineState); ok { | |||
// If it is the compensation of the subState machine, | |||
// directly call the state machine's compensate method | |||
stateMachineEngine, ok := processContext.GetVariable(constant.VarNameStateMachineEngine).(core.StateMachineEngine) | |||
if !ok { | |||
handleResultErr(errors.New("invalid stateMachineEngine type from processContext")) | |||
return nil | |||
} | |||
result, resultErr = s.compensateSubStateMachine(ctx, processContext, serviceTaskStateImpl, input, | |||
stateInstance, stateMachineEngine) | |||
if resultErr != nil { | |||
handleResultErr(resultErr) | |||
return nil | |||
} | |||
} else { | |||
stateMachineConfig, ok := processContext.GetVariable(constant.VarNameStateMachineConfig).(core.StateMachineConfig) | |||
if !ok { | |||
handleResultErr(errors.New("invalid stateMachineConfig type from processContext")) | |||
return nil | |||
} | |||
serviceInvoker := stateMachineConfig.ServiceInvokerManager().ServiceInvoker(serviceTaskStateImpl.ServiceType()) | |||
if serviceInvoker == nil { | |||
resultErr = exception.NewEngineExecutionException(seataErrors.ObjectNotExists, | |||
"No such ServiceInvoker["+serviceTaskStateImpl.ServiceType()+"]", nil) | |||
handleResultErr(resultErr) | |||
return nil | |||
} | |||
result, resultErr = serviceInvoker.Invoke(ctx, input, serviceTaskStateImpl) | |||
if resultErr != nil { | |||
handleResultErr(resultErr) | |||
return nil | |||
} | |||
} | |||
log.Debugf("<<<<<<<<<<<<<<<<<<<<<< State[%s], ServiceName[%s], Method[%s] Execute finish. result: %s", | |||
serviceTaskStateImpl.Name(), serviceName, methodName, result) | |||
if result != nil { | |||
stateInstance.SetOutputParams(result) | |||
hierarchicalProcessContext, ok := processContext.(core.HierarchicalProcessContext) | |||
if !ok { | |||
handleResultErr(errors.New("invalid hierarchical process context type from processContext")) | |||
return nil | |||
} | |||
hierarchicalProcessContext.SetVariable(constant.VarNameOutputParams, result) | |||
} | |||
return nil | |||
} | |||
func (s *ServiceTaskStateHandler) StateHandlerInterceptorList() []core.StateHandlerInterceptor { | |||
return s.interceptors | |||
} | |||
func (s *ServiceTaskStateHandler) RegistryStateHandlerInterceptor(stateHandlerInterceptor core.StateHandlerInterceptor) { | |||
s.interceptors = append(s.interceptors, stateHandlerInterceptor) | |||
} | |||
func (s *ServiceTaskStateHandler) compensateSubStateMachine(ctx context.Context, processContext core.ProcessContext, | |||
serviceTaskState state.ServiceTaskState, input any, instance statelang.StateInstance, | |||
machineEngine core.StateMachineEngine) (any, error) { | |||
subStateMachineParentId, ok := processContext.GetVariable(serviceTaskState.Name() + constant.VarNameSubMachineParentId).(string) | |||
if !ok { | |||
return nil, errors.New("invalid subStateMachineParentId type from processContext") | |||
} | |||
if subStateMachineParentId == "" { | |||
return nil, exception.NewEngineExecutionException(seataErrors.ObjectNotExists, | |||
"sub statemachine parentId is required", nil) | |||
} | |||
stateMachineConfig := processContext.GetVariable(constant.VarNameStateMachineConfig).(core.StateMachineConfig) | |||
subInst, err := stateMachineConfig.StateLogStore().GetStateMachineInstanceByParentId(subStateMachineParentId) | |||
if err != nil { | |||
return nil, err | |||
} | |||
if subInst == nil || len(subInst) == 0 { | |||
return nil, exception.NewEngineExecutionException(seataErrors.ObjectNotExists, | |||
"cannot find sub statemachine instance by parentId:"+subStateMachineParentId, nil) | |||
} | |||
subStateMachineInstId := subInst[0].ID() | |||
log.Debugf(">>>>>>>>>>>>>>>>>>>>>> Start to compensate sub statemachine [id:%s]", subStateMachineInstId) | |||
startParams := make(map[string]any) | |||
if inputList, ok := input.([]any); ok { | |||
if len(inputList) > 0 { | |||
startParams = inputList[0].(map[string]any) | |||
} | |||
} else if inputMap, ok := input.(map[string]any); ok { | |||
startParams = inputMap | |||
} | |||
compensateInst, err := machineEngine.Compensate(ctx, subStateMachineInstId, startParams) | |||
instance.SetStatus(compensateInst.CompensationStatus()) | |||
log.Debugf("<<<<<<<<<<<<<<<<<<<<<< Compensate sub statemachine [id:%s] finished with status[%s], "+"compensateState[%s]", | |||
subStateMachineInstId, compensateInst.Status(), compensateInst.CompensationStatus()) | |||
return compensateInst.EndParams(), nil | |||
} |
@@ -1,4 +1,4 @@ | |||
package process_ctrl | |||
package process | |||
type ProcessType string | |||
@@ -64,7 +64,7 @@ func NewCompensateSubStateMachineStateParser() *CompensateSubStateMachineStatePa | |||
} | |||
func (c CompensateSubStateMachineStateParser) StateType() string { | |||
return constant.CompensateSubMachine | |||
return constant.StateTypeCompensateSubMachine | |||
} | |||
func (c CompensateSubStateMachineStateParser) Parse(stateName string, stateMap map[string]interface{}) (statelang.State, error) { | |||
@@ -0,0 +1,22 @@ | |||
package state | |||
import ( | |||
"github.com/seata/seata-go/pkg/saga/statemachine/constant" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||
) | |||
type LoopStartState interface { | |||
statelang.State | |||
} | |||
type LoopStartStateImpl struct { | |||
*statelang.BaseState | |||
} | |||
func NewLoopStartStateImpl() *LoopStartStateImpl { | |||
baseState := statelang.NewBaseState() | |||
baseState.SetType(constant.StateTypeLoopStart) | |||
return &LoopStartStateImpl{ | |||
BaseState: baseState, | |||
} | |||
} |
@@ -56,7 +56,7 @@ func NewCompensateSubStateMachineStateImpl() *CompensateSubStateMachineStateImpl | |||
ServiceTaskStateImpl: NewServiceTaskStateImpl(), | |||
hashcode: uuid.String(), | |||
} | |||
c.SetType(constant.CompensateSubMachine) | |||
c.SetType(constant.StateTypeCompensateSubMachine) | |||
return c | |||
} | |||
@@ -38,7 +38,7 @@ type Loop interface { | |||
type ExceptionMatch interface { | |||
Exceptions() []string | |||
// TODO: go dose not support get reflect.Type by string, not use it now | |||
ExceptionTypes() []reflect.Type | |||
SetExceptionTypes(ExceptionTypes []reflect.Type) | |||
@@ -79,7 +79,9 @@ type AbstractTaskState struct { | |||
loop Loop | |||
catches []ExceptionMatch | |||
input []interface{} | |||
inputExpressions []interface{} | |||
output map[string]interface{} | |||
outputExpressions map[string]interface{} | |||
compensatePersistModeUpdate bool | |||
retryPersistModeUpdate bool | |||
forCompensation bool | |||
@@ -96,6 +98,22 @@ func NewAbstractTaskState() *AbstractTaskState { | |||
} | |||
} | |||
func (a *AbstractTaskState) InputExpressions() []interface{} { | |||
return a.inputExpressions | |||
} | |||
func (a *AbstractTaskState) SetInputExpressions(inputExpressions []interface{}) { | |||
a.inputExpressions = inputExpressions | |||
} | |||
func (a *AbstractTaskState) OutputExpressions() map[string]interface{} { | |||
return a.outputExpressions | |||
} | |||
func (a *AbstractTaskState) SetOutputExpressions(outputExpressions map[string]interface{}) { | |||
a.outputExpressions = outputExpressions | |||
} | |||
func (a *AbstractTaskState) Input() []interface{} { | |||
return a.input | |||
} | |||
@@ -71,9 +71,9 @@ type StateMachineInstance interface { | |||
SetBusinessKey(businessKey string) | |||
Error() error | |||
Exception() error | |||
SetError(err error) | |||
SetException(err error) | |||
StartParams() map[string]interface{} | |||
@@ -83,6 +83,8 @@ type StateMachineInstance interface { | |||
SetEndParams(endParams map[string]interface{}) | |||
Context() map[string]interface{} | |||
PutContext(key string, value interface{}) | |||
SetContext(context map[string]interface{}) | |||
@@ -115,7 +117,7 @@ type StateMachineInstanceImpl struct { | |||
startedTime time.Time | |||
endTime time.Time | |||
updatedTime time.Time | |||
err error | |||
exception error | |||
serializedError interface{} | |||
endParams map[string]interface{} | |||
serializedEndParams interface{} | |||
@@ -247,12 +249,12 @@ func (s *StateMachineInstanceImpl) SetBusinessKey(businessKey string) { | |||
s.businessKey = businessKey | |||
} | |||
func (s *StateMachineInstanceImpl) Error() error { | |||
return s.err | |||
func (s *StateMachineInstanceImpl) Exception() error { | |||
return s.exception | |||
} | |||
func (s *StateMachineInstanceImpl) SetError(err error) { | |||
s.err = err | |||
func (s *StateMachineInstanceImpl) SetException(err error) { | |||
s.exception = err | |||
} | |||
func (s *StateMachineInstanceImpl) StartParams() map[string]interface{} { | |||
@@ -271,6 +273,10 @@ func (s *StateMachineInstanceImpl) SetEndParams(endParams map[string]interface{} | |||
s.endParams = endParams | |||
} | |||
func (s *StateMachineInstanceImpl) Context() map[string]interface{} { | |||
return s.context | |||
} | |||
func (s *StateMachineInstanceImpl) PutContext(key string, value interface{}) { | |||
s.contextMutex.Lock() | |||
defer s.contextMutex.Unlock() | |||
@@ -6,7 +6,7 @@ import ( | |||
"fmt" | |||
"github.com/pkg/errors" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/constant" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/engine/process_ctrl" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/engine/core" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/engine/sequence" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/engine/serializer" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||
@@ -89,7 +89,7 @@ func NewStateLogStore(db *sql.DB, tablePrefix string) *StateLogStore { | |||
} | |||
func (s *StateLogStore) RecordStateMachineStarted(ctx context.Context, machineInstance statelang.StateMachineInstance, | |||
context process_ctrl.ProcessContext) error { | |||
context core.ProcessContext) error { | |||
if machineInstance == nil { | |||
return nil | |||
} | |||
@@ -133,15 +133,15 @@ func (s *StateLogStore) RecordStateMachineStarted(ctx context.Context, machineIn | |||
} | |||
func (s *StateLogStore) RecordStateMachineFinished(ctx context.Context, machineInstance statelang.StateMachineInstance, | |||
context process_ctrl.ProcessContext) error { | |||
context core.ProcessContext) error { | |||
if machineInstance == nil { | |||
return nil | |||
} | |||
endParams := machineInstance.EndParams() | |||
if statelang.SU == machineInstance.Status() && machineInstance.Error() != nil { | |||
machineInstance.SetError(nil) | |||
if statelang.SU == machineInstance.Status() && machineInstance.Exception() != nil { | |||
machineInstance.SetException(nil) | |||
} | |||
serializedEndParams, err := s.paramsSerializer.Serialize(endParams) | |||
@@ -149,7 +149,7 @@ func (s *StateLogStore) RecordStateMachineFinished(ctx context.Context, machineI | |||
return err | |||
} | |||
machineInstance.SetSerializedEndParams(serializedEndParams) | |||
serializedError, err := s.errorSerializer.Serialize(machineInstance.Error()) | |||
serializedError, err := s.errorSerializer.Serialize(machineInstance.Exception()) | |||
if err != nil { | |||
return err | |||
} | |||
@@ -171,7 +171,7 @@ func (s *StateLogStore) RecordStateMachineFinished(ctx context.Context, machineI | |||
} | |||
func (s *StateLogStore) RecordStateMachineRestarted(ctx context.Context, machineInstance statelang.StateMachineInstance, | |||
context process_ctrl.ProcessContext) error { | |||
context core.ProcessContext) error { | |||
if machineInstance == nil { | |||
return nil | |||
} | |||
@@ -190,7 +190,7 @@ func (s *StateLogStore) RecordStateMachineRestarted(ctx context.Context, machine | |||
} | |||
func (s *StateLogStore) RecordStateStarted(ctx context.Context, stateInstance statelang.StateInstance, | |||
context process_ctrl.ProcessContext) error { | |||
context core.ProcessContext) error { | |||
if stateInstance == nil { | |||
return nil | |||
} | |||
@@ -235,7 +235,7 @@ func (s *StateLogStore) RecordStateStarted(ctx context.Context, stateInstance st | |||
return nil | |||
} | |||
func (s *StateLogStore) isUpdateMode(instance statelang.StateInstance, context process_ctrl.ProcessContext) bool { | |||
func (s *StateLogStore) isUpdateMode(instance statelang.StateInstance, context core.ProcessContext) bool { | |||
//TODO implement me, add forward logic | |||
return false | |||
} | |||
@@ -293,7 +293,7 @@ func (s *StateLogStore) getIdIndex(stateInstanceId string, separator string) int | |||
} | |||
func (s *StateLogStore) RecordStateFinished(ctx context.Context, stateInstance statelang.StateInstance, | |||
context process_ctrl.ProcessContext) error { | |||
context core.ProcessContext) error { | |||
if stateInstance == nil { | |||
return nil | |||
} | |||
@@ -372,7 +372,7 @@ func (s *StateLogStore) deserializeStateMachineParamsAndException(stateMachineIn | |||
if err != nil { | |||
return err | |||
} | |||
stateMachineInstance.SetError(deserializedError) | |||
stateMachineInstance.SetException(deserializedError) | |||
} | |||
serializedStartParams := stateMachineInstance.SerializedStartParams() |
@@ -5,19 +5,19 @@ import ( | |||
"fmt" | |||
"github.com/pkg/errors" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/constant" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/engine" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/engine/process_ctrl" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/engine/core" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/process_ctrl/process" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||
"github.com/stretchr/testify/assert" | |||
"testing" | |||
"time" | |||
) | |||
func mockProcessContext(stateMachineName string, stateMachineInstance statelang.StateMachineInstance) process_ctrl.ProcessContext { | |||
ctx := engine.NewProcessContextBuilder(). | |||
WithProcessType(process_ctrl.StateLang). | |||
func mockProcessContext(stateMachineName string, stateMachineInstance statelang.StateMachineInstance) core.ProcessContext { | |||
ctx := core.NewProcessContextBuilder(). | |||
WithProcessType(process.StateLang). | |||
WithOperationName(constant.OperationNameStart). | |||
WithInstruction(process_ctrl.NewStateInstruction(stateMachineName, "000001")). | |||
WithInstruction(core.NewStateInstruction(stateMachineName, "000001")). | |||
WithStateMachineInstance(stateMachineInstance). | |||
Build() | |||
return ctx | |||
@@ -55,7 +55,7 @@ func TestStateLogStore_RecordStateMachineStarted(t *testing.T) { | |||
assert.Equal(t, expected.ID(), actual.ID()) | |||
assert.Equal(t, expected.MachineID(), actual.MachineID()) | |||
assert.Equal(t, fmt.Sprint(expected.StartParams()), fmt.Sprint(actual.StartParams())) | |||
assert.Nil(t, actual.Error()) | |||
assert.Nil(t, actual.Exception()) | |||
assert.Nil(t, actual.SerializedError()) | |||
assert.Equal(t, expected.Status(), actual.Status()) | |||
assert.Equal(t, expected.StartedTime().UnixNano(), actual.StartedTime().UnixNano()) | |||
@@ -73,7 +73,7 @@ func TestStateLogStore_RecordStateMachineFinished(t *testing.T) { | |||
err := stateLogStore.RecordStateMachineStarted(context.Background(), expected, ctx) | |||
assert.Nil(t, err) | |||
expected.SetEndParams(map[string]any{"end": 100}) | |||
expected.SetError(errors.New("this is a test error")) | |||
expected.SetException(errors.New("this is a test error")) | |||
expected.SetStatus(statelang.FA) | |||
expected.SetEndTime(time.Now()) | |||
expected.SetRunning(false) | |||
@@ -86,7 +86,7 @@ func TestStateLogStore_RecordStateMachineFinished(t *testing.T) { | |||
assert.Equal(t, expected.ID(), actual.ID()) | |||
assert.Equal(t, expected.MachineID(), actual.MachineID()) | |||
assert.Equal(t, fmt.Sprint(expected.StartParams()), fmt.Sprint(actual.StartParams())) | |||
assert.Equal(t, "this is a test error", actual.Error().Error()) | |||
assert.Equal(t, "this is a test error", actual.Exception().Error()) | |||
assert.Equal(t, expected.Status(), actual.Status()) | |||
assert.Equal(t, expected.IsRunning(), actual.IsRunning()) | |||
assert.Equal(t, expected.StartedTime().UnixNano(), actual.StartedTime().UnixNano()) | |||
@@ -240,7 +240,7 @@ func TestStateLogStore_GetStateMachineInstanceByBusinessKey(t *testing.T) { | |||
assert.Equal(t, expected.ID(), actual.ID()) | |||
assert.Equal(t, expected.MachineID(), actual.MachineID()) | |||
assert.Equal(t, fmt.Sprint(expected.StartParams()), fmt.Sprint(actual.StartParams())) | |||
assert.Nil(t, actual.Error()) | |||
assert.Nil(t, actual.Exception()) | |||
assert.Nil(t, actual.SerializedError()) | |||
assert.Equal(t, expected.Status(), actual.Status()) | |||
assert.Equal(t, expected.StartedTime().UnixNano(), actual.StartedTime().UnixNano()) | |||
@@ -269,9 +269,9 @@ func TestStateLogStore_GetStateMachineInstanceByParentId(t *testing.T) { | |||
actual := actualList[0] | |||
assert.Equal(t, expected.ID(), actual.ID()) | |||
assert.Equal(t, expected.MachineID(), actual.MachineID()) | |||
// no startParams, endParams and Error | |||
// no startParams, endParams and Exception | |||
assert.NotEqual(t, fmt.Sprint(expected.StartParams()), fmt.Sprint(actual.StartParams())) | |||
assert.Nil(t, actual.Error()) | |||
assert.Nil(t, actual.Exception()) | |||
assert.Nil(t, actual.SerializedError()) | |||
assert.Equal(t, expected.Status(), actual.Status()) | |||
assert.Equal(t, expected.StartedTime().UnixNano(), actual.StartedTime().UnixNano()) |
@@ -0,0 +1,34 @@ | |||
package repository | |||
import ( | |||
"github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||
"io" | |||
) | |||
type StateMachineRepositoryImpl struct { | |||
} | |||
func (s StateMachineRepositoryImpl) GetStateMachineById(stateMachineId string) (statelang.StateMachine, error) { | |||
//TODO implement me | |||
panic("implement me") | |||
} | |||
func (s StateMachineRepositoryImpl) GetStateMachineByNameAndTenantId(stateMachineName string, tenantId string) (statelang.StateMachine, error) { | |||
//TODO implement me | |||
panic("implement me") | |||
} | |||
func (s StateMachineRepositoryImpl) GetLastVersionStateMachine(stateMachineName string, tenantId string) (statelang.StateMachine, error) { | |||
//TODO implement me | |||
panic("implement me") | |||
} | |||
func (s StateMachineRepositoryImpl) RegistryStateMachine(machine statelang.StateMachine) error { | |||
//TODO implement me | |||
panic("implement me") | |||
} | |||
func (s StateMachineRepositoryImpl) RegistryStateMachineByReader(reader io.Reader) error { | |||
//TODO implement me | |||
panic("implement me") | |||
} |
@@ -361,7 +361,7 @@ func TestBeginNewGtx(t *testing.T) { | |||
assert.Equal(t, message.GlobalStatusBegin, *GetTxStatus(ctx)) | |||
// case return error | |||
err := errors.New("Mock Error") | |||
err := errors.New("Mock Exception") | |||
gomonkey.ApplyMethod(reflect.TypeOf(GetGlobalTransactionManager()), "Begin", | |||
func(_ *GlobalTransactionManager, ctx context.Context, timeout time.Duration) error { | |||
return err | |||
@@ -17,7 +17,10 @@ | |||
package collection | |||
import "strings" | |||
import ( | |||
"container/list" | |||
"strings" | |||
) | |||
const ( | |||
KvSplit = "=" | |||
@@ -81,3 +84,42 @@ func DecodeMap(data []byte) map[string]string { | |||
return ctxMap | |||
} | |||
type Stack struct { | |||
list *list.List | |||
} | |||
func NewStack() *Stack { | |||
list := list.New() | |||
return &Stack{list} | |||
} | |||
func (stack *Stack) Push(value interface{}) { | |||
stack.list.PushBack(value) | |||
} | |||
func (stack *Stack) Pop() interface{} { | |||
e := stack.list.Back() | |||
if e != nil { | |||
stack.list.Remove(e) | |||
return e.Value | |||
} | |||
return nil | |||
} | |||
func (stack *Stack) Peak() interface{} { | |||
e := stack.list.Back() | |||
if e != nil { | |||
return e.Value | |||
} | |||
return nil | |||
} | |||
func (stack *Stack) Len() int { | |||
return stack.list.Len() | |||
} | |||
func (stack *Stack) Empty() bool { | |||
return stack.list.Len() == 0 | |||
} |
@@ -57,3 +57,26 @@ func TestEncodeDecodeMap(t *testing.T) { | |||
}) | |||
} | |||
} | |||
func TestStack(t *testing.T) { | |||
stack := NewStack() | |||
stack.Push(1) | |||
stack.Push(2) | |||
stack.Push(3) | |||
stack.Push(4) | |||
len := stack.Len() | |||
if len != 4 { | |||
t.Errorf("stack.Len() failed. Got %d, expected 4.", len) | |||
} | |||
value := stack.Peak().(int) | |||
if value != 4 { | |||
t.Errorf("stack.Peak() failed. Got %d, expected 4.", value) | |||
} | |||
value = stack.Pop().(int) | |||
if value != 4 { | |||
t.Errorf("stack.Pop() failed. Got %d, expected 4.", value) | |||
} | |||
} |
@@ -100,4 +100,15 @@ const ( | |||
// FencePhaseError have fence phase but is not illegal value | |||
FencePhaseError | |||
// ObjectNotExists object not exists | |||
ObjectNotExists | |||
// StateMachineInstanceNotExists State machine instance not exists | |||
StateMachineInstanceNotExists | |||
// ContextVariableReplayFailed Context variable replay failed | |||
ContextVariableReplayFailed | |||
// InvalidParameter Context variable replay failed | |||
InvalidParameter | |||
// OperationDenied Operation denied | |||
OperationDenied | |||
) |