@@ -3,20 +3,62 @@ package redis
3
3
import (
4
4
"context"
5
5
"encoding/json"
6
- "log"
6
+ "strconv"
7
+ "time"
7
8
8
9
"github.com/cschleiden/go-workflows/backend"
9
10
"github.com/cschleiden/go-workflows/backend/redis/taskqueue"
10
11
"github.com/cschleiden/go-workflows/internal/core"
11
12
"github.com/cschleiden/go-workflows/internal/history"
12
13
"github.com/cschleiden/go-workflows/internal/task"
13
14
"github.com/cschleiden/go-workflows/workflow"
15
+ "github.com/go-redis/redis/v8"
14
16
"github.com/pkg/errors"
15
17
)
16
18
19
+ type futureEvent struct {
20
+ Instance * core.WorkflowInstance `json:"instance,omitempty"`
21
+ Event * history.Event `json:"event,omitempty"`
22
+ }
23
+
24
+ // KEYS[1] - future event set key
25
+ // ARGV[1] - current timestamp for zrange
26
+ var futureEventsCmd = redis .NewScript (`
27
+ local events = redis.call("ZRANGEBYSCORE", KEYS[1], "-inf", ARGV[1])
28
+ redis.call("ZREMRANGEBYSCORE", KEYS[1], "-inf", ARGV[1])
29
+ return events
30
+ ` )
31
+
17
32
func (rb * redisBackend ) GetWorkflowTask (ctx context.Context ) (* task.Workflow , error ) {
18
- // TODO: Check for timer events, and add them to pending events if required
33
+ // Check for future events
34
+ now := time .Now ().Unix ()
35
+ nowStr := strconv .Itoa (int (now ))
36
+
37
+ result , err := futureEventsCmd .Run (ctx , rb .rdb , []string {futureEventsKey ()}, nowStr ).Result ()
38
+ if err != nil {
39
+ return nil , errors .Wrap (err , "could not check future events" )
40
+ }
41
+
42
+ for _ , eventR := range result .([]interface {}) {
43
+ eventStr := eventR .(string )
44
+ var event futureEvent
45
+ if err := json .Unmarshal ([]byte (eventStr ), & event ); err != nil {
46
+ return nil , errors .Wrap (err , "could not unmarshal event" )
47
+ }
48
+
49
+ if err := addEventToStream (ctx , rb .rdb , pendingEventsKey (event .Instance .InstanceID ), event .Event ); err != nil {
50
+ return nil , errors .Wrap (err , "could not add future event to stream" )
51
+ }
52
+
53
+ // Instance now has at least one pending event, try to queue task
54
+ if _ , err := rb .workflowQueue .Enqueue (ctx , event .Instance .InstanceID , nil ); err != nil {
55
+ if err != taskqueue .ErrTaskAlreadyInQueue {
56
+ return nil , errors .Wrap (err , "could not queue workflow" )
57
+ }
58
+ }
59
+ }
19
60
61
+ // Try to get a workflow task
20
62
instanceTask , err := rb .workflowQueue .Dequeue (ctx , rb .options .WorkflowLockTimeout , rb .options .BlockTimeout )
21
63
if err != nil {
22
64
return nil , err
@@ -68,6 +110,7 @@ func (rb *redisBackend) GetWorkflowTask(ctx context.Context) (*task.Workflow, er
68
110
}
69
111
70
112
// Remove all pending events
113
+ // TODO: What happens if the worker dies and this task gets picked up by another one?
71
114
rb .rdb .XTrim (ctx , pendingEventsKey (instanceTask .ID ), 0 )
72
115
73
116
return & task.Workflow {
@@ -102,21 +145,45 @@ func (rb *redisBackend) CompleteWorkflowTask(ctx context.Context, taskID string,
102
145
103
146
for targetInstance , events := range groupedEvents {
104
147
if instance .InstanceID != targetInstance .InstanceID {
105
- // Create new instance
148
+ // Try to create a new instance
106
149
if err := createInstance (ctx , rb .rdb , targetInstance , true ); err != nil {
107
150
return err
108
151
}
109
152
}
110
153
111
154
// Insert pending events for target instance
155
+ addedPendingEvent := false
156
+
112
157
for _ , event := range events {
113
- if err := addEventToStream (ctx , rb .rdb , pendingEventsKey (targetInstance .InstanceID ), & event ); err != nil {
114
- return err
158
+ if event .VisibleAt != nil {
159
+ // Add future event in sorted set
160
+ futureEvent := & futureEvent {
161
+ Instance : targetInstance ,
162
+ Event : & event ,
163
+ }
164
+
165
+ eventData , err := json .Marshal (futureEvent )
166
+ if err != nil {
167
+ return err
168
+ }
169
+
170
+ if err := rb .rdb .ZAdd (ctx , futureEventsKey (), & redis.Z {
171
+ Member : eventData ,
172
+ Score : float64 (event .VisibleAt .Unix ()),
173
+ }).Err (); err != nil {
174
+ return errors .Wrap (err , "could not add future event" )
175
+ }
176
+ } else {
177
+ // Add pending event to stream
178
+ if err := addEventToStream (ctx , rb .rdb , pendingEventsKey (targetInstance .InstanceID ), & event ); err != nil {
179
+ return err
180
+ }
181
+
182
+ addedPendingEvent = true
115
183
}
116
184
}
117
185
118
- // TODO: Delay unlocking the current instance. Can we find a better way here?
119
- if targetInstance != instance {
186
+ if addedPendingEvent && targetInstance != instance {
120
187
if _ , err := rb .workflowQueue .Enqueue (ctx , targetInstance .InstanceID , nil ); err != nil {
121
188
if err != taskqueue .ErrTaskAlreadyInQueue {
122
189
return errors .Wrap (err , "could not add instance to locked instances set" )
@@ -153,7 +220,7 @@ func (rb *redisBackend) CompleteWorkflowTask(ctx context.Context, taskID string,
153
220
return errors .Wrap (err , "could not complete workflow task" )
154
221
}
155
222
156
- // If there are pending events, enqueue the instance again
223
+ // If there are pending events, queue the instance again
157
224
pendingCount , err := rb .rdb .XLen (ctx , pendingEventsKey (instance .InstanceID )).Result ()
158
225
if err != nil {
159
226
return errors .Wrap (err , "could not read event stream" )
@@ -167,8 +234,6 @@ func (rb *redisBackend) CompleteWorkflowTask(ctx context.Context, taskID string,
167
234
}
168
235
}
169
236
170
- log .Println ("Unlocked workflow task" , instance .InstanceID )
171
-
172
237
return nil
173
238
}
174
239
0 commit comments