1
1
package rabbitmq
2
2
3
3
import (
4
+ "context"
4
5
"errors"
5
6
"fmt"
6
7
"sync"
@@ -32,6 +33,7 @@ type Consumer struct {
32
33
reconnectErrCh <- chan error
33
34
closeConnectionToManagerCh chan <- struct {}
34
35
options ConsumerOptions
36
+ handlerMux * sync.RWMutex
35
37
36
38
isClosedMux * sync.RWMutex
37
39
isClosed bool
@@ -89,6 +91,14 @@ func (consumer *Consumer) Run(handler Handler) error {
89
91
return err
90
92
}
91
93
94
+ handler = func (d Delivery ) (action Action ) {
95
+ if ! consumer .handlerMux .TryRLock () {
96
+ return NackRequeue
97
+ }
98
+ defer consumer .handlerMux .RUnlock ()
99
+ return handler (d )
100
+ }
101
+
92
102
for err := range consumer .reconnectErrCh {
93
103
consumer .options .Logger .Infof ("successful consumer recovery from: %v" , err )
94
104
err = consumer .startGoroutines (
@@ -104,10 +114,26 @@ func (consumer *Consumer) Run(handler Handler) error {
104
114
}
105
115
106
116
// Close cleans up resources and closes the consumer.
117
+ // It waits for handler to finish before returning by default
118
+ // (use WithConsumerOptionsForceShutdown option to disable this behavior).
119
+ // Use CloseWithContext to specify a context to cancel the handler completion.
107
120
// It does not close the connection manager, just the subscription
108
121
// to the connection manager and the consuming goroutines.
109
122
// Only call once.
110
123
func (consumer * Consumer ) Close () {
124
+ if consumer .options .CloseGracefully {
125
+ consumer .options .Logger .Infof ("waiting for handler to finish..." )
126
+ err := consumer .waitForHandlerCompletion (context .Background ())
127
+ if err != nil {
128
+ consumer .options .Logger .Warnf ("error while waiting for handler to finish: %v" , err )
129
+ }
130
+ }
131
+
132
+ consumer .cleanupResources ()
133
+
134
+ }
135
+
136
+ func (consumer * Consumer ) cleanupResources () {
111
137
consumer .isClosedMux .Lock ()
112
138
defer consumer .isClosedMux .Unlock ()
113
139
consumer .isClosed = true
@@ -124,6 +150,24 @@ func (consumer *Consumer) Close() {
124
150
}()
125
151
}
126
152
153
+ // CloseWithContext cleans up resources and closes the consumer.
154
+ // It waits for handler to finish before returning
155
+ // (use WithConsumerOptionsForceShutdown option to disable this behavior).
156
+ // Use the context to cancel the handler completion.
157
+ // CloseWithContext does not close the connection manager, just the subscription
158
+ // to the connection manager and the consuming goroutines.
159
+ // Only call once.
160
+ func (consumer * Consumer ) CloseWithContext (ctx context.Context ) {
161
+ if consumer .options .CloseGracefully {
162
+ err := consumer .waitForHandlerCompletion (ctx )
163
+ if err != nil {
164
+ consumer .options .Logger .Warnf ("error while waiting for handler to finish: %v" , err )
165
+ }
166
+ }
167
+
168
+ consumer .cleanupResources ()
169
+ }
170
+
127
171
// startGoroutines declares the queue if it doesn't exist,
128
172
// binds the queue to the routing key(s), and starts the goroutines
129
173
// that will consume from the queue
@@ -213,3 +257,23 @@ func handlerGoroutine(consumer *Consumer, msgs <-chan amqp.Delivery, consumeOpti
213
257
}
214
258
consumer .options .Logger .Infof ("rabbit consumer goroutine closed" )
215
259
}
260
+
261
+ func (consumer * Consumer ) waitForHandlerCompletion (ctx context.Context ) error {
262
+ if ctx == nil {
263
+ ctx = context .Background ()
264
+ } else if ctx .Err () != nil {
265
+ return ctx .Err ()
266
+ }
267
+ c := make (chan struct {})
268
+ go func () {
269
+ consumer .handlerMux .Lock ()
270
+ defer consumer .handlerMux .Unlock ()
271
+ close (c )
272
+ }()
273
+ select {
274
+ case <- ctx .Done ():
275
+ return ctx .Err ()
276
+ case <- c :
277
+ return nil
278
+ }
279
+ }
0 commit comments