1
1
package nsq
2
2
3
3
import (
4
+ "context"
5
+ "encoding/json"
4
6
"runtime"
5
7
"sync"
8
+ "sync/atomic"
6
9
"time"
7
10
8
11
"github.com/appleboy/queue"
@@ -15,16 +18,6 @@ var _ queue.Worker = (*Worker)(nil)
15
18
// Option for queue system
16
19
type Option func (* Worker )
17
20
18
- // Job with NSQ message
19
- type Job struct {
20
- Body []byte
21
- }
22
-
23
- // Bytes get bytes format
24
- func (j * Job ) Bytes () []byte {
25
- return j .Body
26
- }
27
-
28
21
// Worker for NSQ
29
22
type Worker struct {
30
23
q * nsq.Consumer
@@ -36,7 +29,10 @@ type Worker struct {
36
29
addr string
37
30
topic string
38
31
channel string
39
- runFunc func (queue.QueuedMessage , <- chan struct {}) error
32
+ runFunc func (context.Context , queue.QueuedMessage ) error
33
+ logger queue.Logger
34
+ stopFlag int32
35
+ startFlag int32
40
36
}
41
37
42
38
// WithAddr setup the addr of NSQ
@@ -61,7 +57,7 @@ func WithChannel(channel string) Option {
61
57
}
62
58
63
59
// WithRunFunc setup the run func of queue
64
- func WithRunFunc (fn func (queue. QueuedMessage , <- chan struct {} ) error ) Option {
60
+ func WithRunFunc (fn func (context. Context , queue. QueuedMessage ) error ) Option {
65
61
return func (w * Worker ) {
66
62
w .runFunc = fn
67
63
}
@@ -74,6 +70,13 @@ func WithMaxInFlight(num int) Option {
74
70
}
75
71
}
76
72
73
+ // WithLogger set custom logger
74
+ func WithLogger (l queue.Logger ) Option {
75
+ return func (w * Worker ) {
76
+ w .logger = l
77
+ }
78
+ }
79
+
77
80
// NewWorker for struc
78
81
func NewWorker (opts ... Option ) * Worker {
79
82
var err error
@@ -83,7 +86,8 @@ func NewWorker(opts ...Option) *Worker {
83
86
channel : "ch" ,
84
87
maxInFlight : runtime .NumCPU (),
85
88
stop : make (chan struct {}),
86
- runFunc : func (queue.QueuedMessage , <- chan struct {}) error {
89
+ logger : queue .NewLogger (),
90
+ runFunc : func (context.Context , queue.QueuedMessage ) error {
87
91
return nil
88
92
},
89
93
}
@@ -122,33 +126,87 @@ func (s *Worker) AfterRun() error {
122
126
if err != nil {
123
127
panic ("Could not connect nsq server: " + err .Error ())
124
128
}
129
+
130
+ atomic .CompareAndSwapInt32 (& s .startFlag , 0 , 1 )
125
131
})
126
132
127
133
return nil
128
134
}
129
135
136
+ func (s * Worker ) handle (job queue.Job ) error {
137
+ // create channel with buffer size 1 to avoid goroutine leak
138
+ done := make (chan error , 1 )
139
+ panicChan := make (chan interface {}, 1 )
140
+ startTime := time .Now ()
141
+ ctx , cancel := context .WithTimeout (context .Background (), job .Timeout )
142
+ defer cancel ()
143
+
144
+ // run the job
145
+ go func () {
146
+ // handle panic issue
147
+ defer func () {
148
+ if p := recover (); p != nil {
149
+ panicChan <- p
150
+ }
151
+ }()
152
+
153
+ // run custom process function
154
+ done <- s .runFunc (ctx , job )
155
+ }()
156
+
157
+ select {
158
+ case p := <- panicChan :
159
+ panic (p )
160
+ case <- ctx .Done (): // timeout reached
161
+ return ctx .Err ()
162
+ case <- s .stop : // shutdown service
163
+ // cancel job
164
+ cancel ()
165
+
166
+ leftTime := job .Timeout - time .Since (startTime )
167
+ // wait job
168
+ select {
169
+ case <- time .After (leftTime ):
170
+ return context .DeadlineExceeded
171
+ case err := <- done : // job finish
172
+ return err
173
+ case p := <- panicChan :
174
+ panic (p )
175
+ }
176
+ case err := <- done : // job finish
177
+ return err
178
+ }
179
+ }
180
+
130
181
// Run start the worker
131
182
func (s * Worker ) Run () error {
132
183
wg := & sync.WaitGroup {}
184
+ panicChan := make (chan interface {}, 1 )
133
185
s .q .AddHandler (nsq .HandlerFunc (func (msg * nsq.Message ) error {
134
186
wg .Add (1 )
135
- defer wg .Done ()
187
+ defer func () {
188
+ wg .Done ()
189
+ if p := recover (); p != nil {
190
+ panicChan <- p
191
+ }
192
+ }()
136
193
if len (msg .Body ) == 0 {
137
194
// Returning nil will automatically send a FIN command to NSQ to mark the message as processed.
138
195
// In this case, a message with an empty body is simply ignored/discarded.
139
196
return nil
140
197
}
141
198
142
- job := & Job {
143
- Body : msg .Body ,
144
- }
145
-
146
- // run custom process function
147
- return s .runFunc (job , s .stop )
199
+ var data queue.Job
200
+ _ = json .Unmarshal (msg .Body , & data )
201
+ return s .handle (data )
148
202
}))
149
203
150
204
// wait close signal
151
- <- s .stop
205
+ select {
206
+ case <- s .stop :
207
+ case err := <- panicChan :
208
+ s .logger .Error (err )
209
+ }
152
210
153
211
// wait job completed
154
212
wg .Wait ()
@@ -158,9 +216,16 @@ func (s *Worker) Run() error {
158
216
159
217
// Shutdown worker
160
218
func (s * Worker ) Shutdown () error {
219
+ if ! atomic .CompareAndSwapInt32 (& s .stopFlag , 0 , 1 ) {
220
+ return queue .ErrQueueShutdown
221
+ }
222
+
161
223
s .stopOnce .Do (func () {
162
- s .q .Stop ()
163
- s .p .Stop ()
224
+ if atomic .LoadInt32 (& s .startFlag ) == 1 {
225
+ s .q .Stop ()
226
+ s .p .Stop ()
227
+ }
228
+
164
229
close (s .stop )
165
230
})
166
231
return nil
@@ -178,6 +243,10 @@ func (s *Worker) Usage() int {
178
243
179
244
// Queue send notification to queue
180
245
func (s * Worker ) Queue (job queue.QueuedMessage ) error {
246
+ if atomic .LoadInt32 (& s .stopFlag ) == 1 {
247
+ return queue .ErrQueueShutdown
248
+ }
249
+
181
250
err := s .p .Publish (s .topic , job .Bytes ())
182
251
if err != nil {
183
252
return err
0 commit comments