// Poll polls the task list for a task. If there is no task available, nil is // returned. If an error is encountered, no task is returned. func (p *DecisionTaskPoller) Poll(taskReady func(*swf.PollForDecisionTaskOutput) bool) (*swf.PollForDecisionTaskOutput, error) { var resp *swf.PollForDecisionTaskOutput eachPage := func(out *swf.PollForDecisionTaskOutput, lastPage bool) bool { Log.Println("component=DecisionTaskPoller at=decision-task-page") if resp == nil { resp = out } else { resp.Events = append(resp.Events, out.Events...) } shouldContinue := !(lastPage || taskReady(resp)) //stop if last page or task ready return shouldContinue } err := p.client.PollForDecisionTaskPages(&swf.PollForDecisionTaskInput{ Domain: aws.String(p.Domain), Identity: aws.String(p.Identity), ReverseOrder: aws.Bool(true), TaskList: &swf.TaskList{Name: aws.String(p.TaskList)}, }, eachPage) if err != nil { Log.Printf("component=DecisionTaskPoller at=error error=%s", err.Error()) return nil, errors.Trace(err) } if resp != nil && resp.TaskToken != nil { Log.Printf("component=DecisionTaskPoller at=decision-task-received workflow=%s", LS(resp.WorkflowType.Name)) p.logTaskLatency(resp) return resp, nil } Log.Println("component=DecisionTaskPoller at=decision-task-empty-response") return nil, nil }
// 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) } }
// Poll polls the task list for a task. If there is no task available, nil is // returned. If an error is encountered, no task is returned. func (p *DecisionTaskPoller) Poll(taskReady func(*swf.PollForDecisionTaskOutput) bool) (*swf.PollForDecisionTaskOutput, error) { var ( resp *swf.PollForDecisionTaskOutput page int pollId = uuid.New() ) eachPage := func(out *swf.PollForDecisionTaskOutput, _ bool) bool { page++ var ( firstEventId *int64 lastEventId *int64 workflowId string ) if len(out.Events) > 0 { firstEventId = out.Events[0].EventId lastEventId = out.Events[len(out.Events)-1].EventId } if out.WorkflowExecution != nil { workflowId = LS(out.WorkflowExecution.WorkflowId) } else { workflowId = "no-workflow-execution" } Log.Printf("component=DecisionTaskPoller at=decision-task-page poll-id=%q task-list=%q workflow=%q page=%d "+ "PreviousStartedEventId=%s StartedEventId=%s NumEvents=%d FirstEventId=%s LastEventId=%s", pollId, p.TaskList, workflowId, page, LL(out.PreviousStartedEventId), LL(out.StartedEventId), len(out.Events), LL(firstEventId), LL(lastEventId)) if resp == nil { resp = out } else { resp.Events = append(resp.Events, out.Events...) } return !taskReady(resp) } err := p.client.PollForDecisionTaskPages(&swf.PollForDecisionTaskInput{ Domain: aws.String(p.Domain), Identity: aws.String(p.Identity), ReverseOrder: aws.Bool(true), TaskList: &swf.TaskList{Name: aws.String(p.TaskList)}, }, eachPage) if err != nil { Log.Printf("component=DecisionTaskPoller poll-id=%q task-list=%q at=error error=%s", pollId, p.TaskList, err.Error()) return nil, errors.Trace(err) } if resp != nil && resp.TaskToken != nil { Log.Printf("component=DecisionTaskPoller poll-id=%q at=decision-task-received task-list=%q workflow=%q", pollId, p.TaskList, LS(resp.WorkflowExecution.WorkflowId)) p.logTaskLatency(resp) return resp, nil } Log.Printf("component=DecisionTaskPoller at=decision-task-empty-response poll-id=%q task-list=%q", pollId, p.TaskList) return nil, nil }