1
1
package nats
2
2
3
3
import (
4
+ "context"
5
+ "encoding/json"
4
6
"sync"
7
+ "sync/atomic"
8
+ "time"
5
9
6
10
"github.com/appleboy/queue"
7
11
@@ -13,16 +17,6 @@ var _ queue.Worker = (*Worker)(nil)
13
17
// Option for queue system
14
18
type Option func (* Worker )
15
19
16
- // Job with NSQ message
17
- type Job struct {
18
- Body []byte
19
- }
20
-
21
- // Bytes get bytes format
22
- func (j * Job ) Bytes () []byte {
23
- return j .Body
24
- }
25
-
26
20
// Worker for NSQ
27
21
type Worker struct {
28
22
addr string
@@ -31,7 +25,9 @@ type Worker struct {
31
25
client * nats.Conn
32
26
stop chan struct {}
33
27
stopOnce sync.Once
34
- runFunc func (queue.QueuedMessage , <- chan struct {}) error
28
+ runFunc func (context.Context , queue.QueuedMessage ) error
29
+ logger queue.Logger
30
+ stopFlag int32
35
31
}
36
32
37
33
// WithAddr setup the addr of NATS
@@ -56,12 +52,19 @@ func WithQueue(queue string) Option {
56
52
}
57
53
58
54
// WithRunFunc setup the run func of queue
59
- func WithRunFunc (fn func (queue. QueuedMessage , <- chan struct {} ) error ) Option {
55
+ func WithRunFunc (fn func (context. Context , queue. QueuedMessage ) error ) Option {
60
56
return func (w * Worker ) {
61
57
w .runFunc = fn
62
58
}
63
59
}
64
60
61
+ // WithLogger set custom logger
62
+ func WithLogger (l queue.Logger ) Option {
63
+ return func (w * Worker ) {
64
+ w .logger = l
65
+ }
66
+ }
67
+
65
68
// NewWorker for struc
66
69
func NewWorker (opts ... Option ) * Worker {
67
70
var err error
@@ -70,7 +73,7 @@ func NewWorker(opts ...Option) *Worker {
70
73
subj : "foobar" ,
71
74
queue : "foobar" ,
72
75
stop : make (chan struct {}),
73
- runFunc : func (queue. QueuedMessage , <- chan struct {} ) error {
76
+ runFunc : func (context. Context , queue. QueuedMessage ) error {
74
77
return nil
75
78
},
76
79
}
@@ -99,26 +102,81 @@ func (s *Worker) AfterRun() error {
99
102
return nil
100
103
}
101
104
105
+ func (s * Worker ) handle (job queue.Job ) error {
106
+ // create channel with buffer size 1 to avoid goroutine leak
107
+ done := make (chan error , 1 )
108
+ panicChan := make (chan interface {}, 1 )
109
+ startTime := time .Now ()
110
+ ctx , cancel := context .WithTimeout (context .Background (), job .Timeout )
111
+ defer cancel ()
112
+
113
+ // run the job
114
+ go func () {
115
+ // handle panic issue
116
+ defer func () {
117
+ if p := recover (); p != nil {
118
+ panicChan <- p
119
+ }
120
+ }()
121
+
122
+ // run custom process function
123
+ done <- s .runFunc (ctx , job )
124
+ }()
125
+
126
+ select {
127
+ case p := <- panicChan :
128
+ panic (p )
129
+ case <- ctx .Done (): // timeout reached
130
+ return ctx .Err ()
131
+ case <- s .stop : // shutdown service
132
+ // cancel job
133
+ cancel ()
134
+
135
+ leftTime := job .Timeout - time .Since (startTime )
136
+ // wait job
137
+ select {
138
+ case <- time .After (leftTime ):
139
+ return context .DeadlineExceeded
140
+ case err := <- done : // job finish
141
+ return err
142
+ case p := <- panicChan :
143
+ panic (p )
144
+ }
145
+ case err := <- done : // job finish
146
+ return err
147
+ }
148
+ }
149
+
102
150
// Run start the worker
103
151
func (s * Worker ) Run () error {
104
152
wg := & sync.WaitGroup {}
105
-
153
+ panicChan := make ( chan interface {}, 1 )
106
154
_ , err := s .client .QueueSubscribe (s .subj , s .queue , func (m * nats.Msg ) {
107
155
wg .Add (1 )
108
- defer wg .Done ()
109
- job := & Job {
110
- Body : m .Data ,
156
+ defer func () {
157
+ wg .Done ()
158
+ if p := recover (); p != nil {
159
+ panicChan <- p
160
+ }
161
+ }()
162
+
163
+ var data queue.Job
164
+ _ = json .Unmarshal (m .Data , & data )
165
+
166
+ if err := s .handle (data ); err != nil {
167
+ s .logger .Error (err )
111
168
}
112
-
113
- // run custom process function
114
- _ = s .runFunc (job , s .stop )
115
169
})
116
170
if err != nil {
117
171
return err
118
172
}
119
173
120
174
// wait close signal
121
- <- s .stop
175
+ select {
176
+ case <- s .stop :
177
+ case err := <- panicChan :
178
+ s .logger .Error (err )
179
+ }
122
180
123
181
// wait job completed
124
182
wg .Wait ()
@@ -128,9 +186,13 @@ func (s *Worker) Run() error {
128
186
129
187
// Shutdown worker
130
188
func (s * Worker ) Shutdown () error {
189
+ if ! atomic .CompareAndSwapInt32 (& s .stopFlag , 0 , 1 ) {
190
+ return queue .ErrQueueShutdown
191
+ }
192
+
131
193
s .stopOnce .Do (func () {
132
- close (s .stop )
133
194
s .client .Close ()
195
+ close (s .stop )
134
196
})
135
197
return nil
136
198
}
@@ -147,6 +209,10 @@ func (s *Worker) Usage() int {
147
209
148
210
// Queue send notification to queue
149
211
func (s * Worker ) Queue (job queue.QueuedMessage ) error {
212
+ if atomic .LoadInt32 (& s .stopFlag ) == 1 {
213
+ return queue .ErrQueueShutdown
214
+ }
215
+
150
216
err := s .client .Publish (s .subj , job .Bytes ())
151
217
if err != nil {
152
218
return err
0 commit comments