// ErrorStateTick is called when the DecisionTaskPoller receives a PollForDecisionTaskResponse in its polling loop // that contains an error marker in its history. func (f *FSM) ErrorStateTick(decisionTask *swf.PollForDecisionTaskOutput, error *SerializedErrorState, context *FSMContext, data interface{}) (*Outcome, error) { handler := f.errorHandlers[context.State] if handler == nil { handler = f.DecisionErrorHandler } handled, notHandled := handler(context, error.ErrorEvent, data, data, nil) if handled == nil { return nil, notHandled } //todo we are assuming all history events in the range //error.EarliestUnprocessedEventId to error.LatestUnprocessedEventId //are in the decisionTaks.History filteredDecisionTask := new(swf.PollForDecisionTaskOutput) s, e := f.systemSerializer.Serialize(decisionTask) if e != nil { return nil, e } e = f.systemSerializer.Deserialize(s, filteredDecisionTask) if e != nil { return nil, e } filtered := make([]*swf.HistoryEvent, 0) for _, h := range decisionTask.Events { if f.isErrorMarker(h) { continue } filtered = append(filtered, h) } filteredDecisionTask.Events = filtered filteredDecisionTask.StartedEventId = &error.LatestUnprocessedEventId filteredDecisionTask.PreviousStartedEventId = &error.EarliestUnprocessedEventId _, decisions, serializedState, err := f.Tick(filteredDecisionTask) if err != nil { data := f.zeroStateData() f.Deserialize(serializedState.StateData, data) return &Outcome{ State: serializedState.StateName, Decisions: decisions, Data: data, }, nil } return nil, err }
func TestInterceptors(t *testing.T) { calledAfter := false calledBefore := false calledBeforeCtx := false interceptor := &FuncInterceptor{ BeforeTaskFn: func(decision *swf.PollForDecisionTaskOutput) { calledBefore = true }, BeforeDecisionFn: func(decision *swf.PollForDecisionTaskOutput, ctx *FSMContext, outcome *Outcome) { outcome.Decisions = append(outcome.Decisions, ctx.CompleteWorkflowDecision(&TestData{})) outcome.Decisions = append(outcome.Decisions, ctx.CompleteWorkflowDecision(&TestData{})) calledBeforeCtx = true }, AfterDecisionFn: func(decision *swf.PollForDecisionTaskOutput, ctx *FSMContext, outcome *Outcome) { if countCompletes(outcome.Decisions) != 2 { t.Fatal("not 2 completes in after") } outcome.Decisions = dedupeCompletes(outcome.Decisions) if countCompletes(outcome.Decisions) != 1 { t.Fatal("not 1 completes in after dedupe") } calledAfter = true }, } fsm := &FSM{ Name: "test-fsm", DataType: TestData{}, DecisionInterceptor: interceptor, Serializer: JSONStateSerializer{}, SystemSerializer: JSONStateSerializer{}, } fsm.AddInitialState(&FSMState{Name: "initial", Decider: func(ctx *FSMContext, e *swf.HistoryEvent, d interface{}) Outcome { return Outcome{State: "initial", Data: d, Decisions: []*swf.Decision{}} }}) decisionTask := new(swf.PollForDecisionTaskOutput) decisionTask.WorkflowExecution = new(swf.WorkflowExecution) decisionTask.WorkflowType = &swf.WorkflowType{Name: S("test"), Version: S("1")} decisionTask.WorkflowExecution.RunId = S("run") decisionTask.WorkflowExecution.WorkflowId = S("wf") decisionTask.PreviousStartedEventId = I(5) decisionTask.StartedEventId = I(15) decisionTask.Events = []*swf.HistoryEvent{ { EventId: I(10), EventType: S("WorkflowExecutionStarted"), WorkflowExecutionStartedEventAttributes: &swf.WorkflowExecutionStartedEventAttributes{ Input: StartFSMWorkflowInput(fsm, new(TestData)), }, }, } _, ds, _, _ := fsm.Tick(decisionTask) if calledBefore == false { t.Fatalf("before not called") } if calledBeforeCtx == false { t.Fatalf("before context not called") } if calledAfter == false { t.Fatalf("after not called") } if countCompletes(ds) != 1 { t.Fatalf("Deduping completes failed %v", ds) } }