@@ -40,17 +40,18 @@ type WorkflowExecutor interface {
40
40
}
41
41
42
42
type executor struct {
43
- registry * Registry
44
- historyProvider WorkflowHistoryProvider
45
- workflow * workflow
46
- workflowTracer * workflowtracer.WorkflowTracer
47
- workflowState * workflowstate.WfState
48
- workflowCtx sync.Context
49
- workflowCtxCancel sync.CancelFunc
50
- clock clock.Clock
51
- logger log.Logger
52
- tracer trace.Tracer
53
- lastSequenceID int64
43
+ registry * Registry
44
+ historyProvider WorkflowHistoryProvider
45
+ workflow * workflow
46
+ workflowTracer * workflowtracer.WorkflowTracer
47
+ workflowState * workflowstate.WfState
48
+ workflowCtx sync.Context
49
+ workflowCtxCancel sync.CancelFunc
50
+ clock clock.Clock
51
+ logger log.Logger
52
+ tracer trace.Tracer
53
+ lastSequenceID int64
54
+ wfStartedEventSeen bool
54
55
}
55
56
56
57
func NewExecutor (logger log.Logger , tracer trace.Tracer , registry * Registry , historyProvider WorkflowHistoryProvider , instance * core.WorkflowInstance , clock clock.Clock ) (WorkflowExecutor , error ) {
@@ -69,15 +70,16 @@ func NewExecutor(logger log.Logger, tracer trace.Tracer, registry *Registry, his
69
70
)
70
71
71
72
return & executor {
72
- registry : registry ,
73
- historyProvider : historyProvider ,
74
- workflowTracer : wfTracer ,
75
- workflowState : s ,
76
- workflowCtx : wfCtx ,
77
- workflowCtxCancel : cancel ,
78
- clock : clock ,
79
- logger : logger ,
80
- tracer : tracer ,
73
+ registry : registry ,
74
+ historyProvider : historyProvider ,
75
+ workflowTracer : wfTracer ,
76
+ workflowState : s ,
77
+ workflowCtx : wfCtx ,
78
+ workflowCtxCancel : cancel ,
79
+ clock : clock ,
80
+ logger : logger ,
81
+ tracer : tracer ,
82
+ wfStartedEventSeen : false ,
81
83
}, nil
82
84
}
83
85
@@ -141,6 +143,36 @@ func (e *executor) ExecuteTask(ctx context.Context, t *task.Workflow) (*Executio
141
143
return nil , fmt .Errorf ("task has older history than current state, cannot execute" )
142
144
}
143
145
146
+ // Potentially reorder new events here, protecting against
147
+ // cases in which new events are received for a workflow instance
148
+ // before the scheduler for that workflow instance has been
149
+ // created. To reorder, we find the WorkflowExecutionStarted
150
+ // event, then move it to the first position in t.NewEvents.
151
+ // t.NewEvents is modified in-place.
152
+ // See: https://github.com/cschleiden/go-workflows/issues/143
153
+ if ! e .wfStartedEventSeen {
154
+ for i , ev := range t .NewEvents {
155
+ if ev .Type == history .EventType_WorkflowExecutionStarted {
156
+ if i > 0 {
157
+ // Shift elements before the WorkflowExecutionStarted
158
+ // event 1 index right, making space at index 0 to reinsert
159
+ // the WorkflowExecutionStarted event. Shifting instead of
160
+ // re-slicing and calling append() twice, i.e.:
161
+ // t.NewEvents = append(t.NewEvents[0:i], t.NewEvents[i+1:]...)
162
+ // t.NewEvents = append([]history.Event{ev}, t.NewEvents...)
163
+ // is faster and avoids the possibility of copying
164
+ // slices if t.NewEvents is large
165
+ for j := i ; j >= 1 ; j -- {
166
+ t .NewEvents [j ] = t .NewEvents [j - 1 ]
167
+ }
168
+ t .NewEvents [0 ] = ev
169
+ }
170
+ e .wfStartedEventSeen = true
171
+ break
172
+ }
173
+ }
174
+ }
175
+
144
176
// Always add a WorkflowTaskStarted event before executing new tasks
145
177
toExecute := []history.Event {e .createNewEvent (history .EventType_WorkflowTaskStarted , & history.WorkflowTaskStartedAttributes {})}
146
178
executedEvents := toExecute
@@ -205,9 +237,9 @@ func (e *executor) ExecuteTask(ctx context.Context, t *task.Workflow) (*Executio
205
237
}, nil
206
238
}
207
239
208
- func (e * executor ) replayHistory (history []history.Event ) error {
240
+ func (e * executor ) replayHistory (h []history.Event ) error {
209
241
e .workflowState .SetReplaying (true )
210
- for _ , event := range history {
242
+ for _ , event := range h {
211
243
if event .SequenceID < e .lastSequenceID {
212
244
e .logger .Panic ("history has older events than current state" )
213
245
}
@@ -216,6 +248,14 @@ func (e *executor) replayHistory(history []history.Event) error {
216
248
return err
217
249
}
218
250
251
+ // If we need to replay history before continuing execution of
252
+ // a new task, the executor must know if WorkflowExecutionStarted
253
+ // was seen during replay so it can determine if events should
254
+ // be reordered before it starts executing events for the new task
255
+ if event .Type == history .EventType_WorkflowExecutionStarted {
256
+ e .wfStartedEventSeen = true
257
+ }
258
+
219
259
e .lastSequenceID = event .SequenceID
220
260
}
221
261
0 commit comments