-
Notifications
You must be signed in to change notification settings - Fork 3
/
task.go
206 lines (168 loc) · 3.66 KB
/
task.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
package axe
import (
"reflect"
"time"
"github.com/globalsign/mgo/bson"
)
// Error is used to signal failed job executions.
type Error struct {
Reason string
Retry bool
}
// Error implements the error interface.
func (c *Error) Error() string {
return c.Reason
}
// E is a short-hand to construct an error.
func E(reason string, retry bool) *Error {
return &Error{
Reason: reason,
Retry: retry,
}
}
// Task describes work that is managed using a job queue.
type Task struct {
// Name is the unique name of the task.
Name string
// Model is the model that holds task related data.
Model Model
// Queue is the queue that is used to manage the jobs.
Queue *Queue
// Handler is the callback called with jobs for processing. The handler
// should return errors formatted with E to properly indicate the status of
// the job. If a task execution is successful the handler might return some
// data that is attached to the job.
Handler func(Model) (bson.M, error)
// Workers defines the number for spawned workers that dequeue and execute
// jobs.
//
// Default: 2.
Workers int
// MaxAttempts defines the maximum attempts to complete a task.
//
// Default: 3
MaxAttempts int
// Interval defines the rate at which the worker will request a job from the
// queue.
//
// Default: 100ms.
Interval time.Duration
// Delay is the time after a failed task is retried.
//
// Default: 1s.
Delay time.Duration
// Timeout is the time after which a task can be dequeue again in case the
// worker was not able to set its status.
//
// Default: 10m.
Timeout time.Duration
}
func (t *Task) start(p *Pool) {
// set default workers
if t.Workers == 0 {
t.Workers = 2
}
// set default max attempts
if t.MaxAttempts == 0 {
t.MaxAttempts = 3
}
// set default interval
if t.Interval == 0 {
t.Interval = 100 * time.Millisecond
}
// set default delay
if t.Delay == 0 {
t.Delay = time.Second
}
// set default timeout
if t.Timeout == 0 {
t.Timeout = 10 * time.Minute
}
// start workers
for i := 0; i < t.Workers; i++ {
go t.worker(p)
}
}
func (t *Task) worker(p *Pool) {
// run forever
for {
// return if closed
select {
case <-p.closed:
return
default:
}
// attempt to get job from queue
job := t.Queue.get(t.Name)
if job == nil {
// wait some time before trying again
select {
case <-time.After(t.Interval):
// continue
case <-p.closed:
return
}
continue
}
// execute job and report errors
err := t.execute(job)
if err != nil {
if p.Reporter != nil {
p.Reporter(err)
}
}
}
}
func (t *Task) execute(job *Job) error {
// get store
store := t.Queue.store.Copy()
defer store.Close()
// dequeue job
job, err := dequeue(store, job.ID(), t.Timeout)
if err != nil {
return err
}
// return if missing (might be dequeued already by another process)
if job == nil {
return nil
}
// instantiate model
data := reflect.New(reflect.TypeOf(t.Model).Elem()).Interface()
// unmarshal data
err = job.Data.Unmarshal(data)
if err != nil {
return err
}
// run handler
result, err := t.Handler(data)
// check error
if e, ok := err.(*Error); ok {
// check retry and attempts
if !e.Retry || job.Attempts >= t.MaxAttempts {
// cancel job
err = cancel(store, job.ID(), e.Reason)
if err != nil {
return err
}
return nil
}
// fail job
err = fail(store, job.ID(), e.Reason, t.Delay)
if err != nil {
return err
}
return nil
}
// handle other errors
if err != nil {
// attempt to fail job
_ = fail(store, job.ID(), err.Error(), t.Delay)
return err
}
// complete job
err = complete(store, job.ID(), result)
if err != nil {
return err
}
return nil
}