@@ -69,6 +69,10 @@ type Server struct {
6969 // channel will be closed when Stop() is called.
7070 stopChan chan struct {}
7171
72+ // restartChan channel is used to signal restarts. It can be set by the user
73+ // so they can restart the server without having to call Stop()/Start()
74+ restartChan chan struct {}
75+
7276 // isRunning is 1 when the server is running.
7377 isRunning int32
7478
@@ -94,6 +98,9 @@ func NewServer(url string) *Server {
9498 errorLog : log .Printf , // use the standard logger default.
9599 //nolint:revive // Keep variables for clarity
96100 debugLog : func (format string , args ... interface {}) {}, // don't print anything default.
101+ // We ensure to always create a channel so we can call `Restart` without
102+ // blocking.
103+ restartChan : make (chan struct {}),
97104 }
98105
99106 server .setDefaults ()
@@ -190,6 +197,14 @@ func (s *Server) WithDebugLogger(f LogFunc) *Server {
190197 return s
191198}
192199
200+ // WithRestartChan will add a channel to the server that will trigger a restart
201+ // when it's triggered.
202+ func (s * Server ) WithRestartChan (ch chan struct {}) * Server {
203+ s .restartChan = ch
204+
205+ return s
206+ }
207+
193208// AddMiddleware will add a ServerMiddleware to the list of middlewares to be
194209// triggered before the handle func for each request.
195210func (s * Server ) AddMiddleware (m ServerMiddlewareFunc ) * Server {
@@ -241,7 +256,7 @@ func (s *Server) ListenAndServe() {
241256 }
242257
243258 for {
244- err := s .listenAndServe ()
259+ shouldRestart , err := s .listenAndServe ()
245260 // If we couldn't run listenAndServe and an error was returned, make
246261 // sure to check if the stopChan was closed - a user might know about
247262 // connection problems and have call Stop(). If the channel isn't
@@ -259,6 +274,17 @@ func (s *Server) ListenAndServe() {
259274 }
260275 }
261276
277+ if shouldRestart {
278+ // We must set up responses again. It's required to close to shut
279+ // down the responders so we let the shutdown process close it and
280+ // then we re-create it here.
281+ s .responses = make (chan processedRequest )
282+
283+ s .debugLog ("server: listener restarting" )
284+
285+ continue
286+ }
287+
262288 s .debugLog ("server: listener exiting gracefully" )
263289
264290 break
@@ -267,7 +293,7 @@ func (s *Server) ListenAndServe() {
267293 atomic .StoreInt32 (& s .isRunning , 0 )
268294}
269295
270- func (s * Server ) listenAndServe () error {
296+ func (s * Server ) listenAndServe () ( bool , error ) {
271297 s .debugLog ("server: starting listener: %s" , s .url )
272298
273299 // We are using two different connections here because:
@@ -277,15 +303,15 @@ func (s *Server) listenAndServe() error {
277303 // -- https://godoc.org/github.com/rabbitmq/amqp091-go#Channel.Consume
278304 inputConn , outputConn , err := createConnections (s .url , s .dialconfig )
279305 if err != nil {
280- return err
306+ return false , err
281307 }
282308
283309 defer inputConn .Close ()
284310 defer outputConn .Close ()
285311
286312 inputCh , outputCh , err := createChannels (inputConn , outputConn )
287313 if err != nil {
288- return err
314+ return false , err
289315 }
290316
291317 defer inputCh .Close ()
@@ -297,7 +323,7 @@ func (s *Server) listenAndServe() error {
297323 false ,
298324 )
299325 if err != nil {
300- return err
326+ return false , err
301327 }
302328
303329 // Notify everyone that the server has started.
@@ -312,7 +338,7 @@ func (s *Server) listenAndServe() error {
312338 // cancel our consumers.
313339 consumerTags , err := s .startConsumers (inputCh , & consumersWg )
314340 if err != nil {
315- return err
341+ return false , err
316342 }
317343
318344 // This WaitGroup will reach 0 when the responder() has finished sending
@@ -322,23 +348,28 @@ func (s *Server) listenAndServe() error {
322348
323349 go s .responder (outputCh , & responderWg )
324350
325- err = monitorAndWait (
351+ shouldRestart , err := monitorAndWait (
352+ s .restartChan ,
326353 s .stopChan ,
327354 inputConn .NotifyClose (make (chan * amqp.Error )),
328355 outputConn .NotifyClose (make (chan * amqp.Error )),
329356 inputCh .NotifyClose (make (chan * amqp.Error )),
330357 outputCh .NotifyClose (make (chan * amqp.Error )),
331358 )
332359 if err != nil {
333- return err
360+ return shouldRestart , err
334361 }
335362
336- s .debugLog ("server: gracefully shutting down" )
363+ if shouldRestart {
364+ s .debugLog ("server: restarting server" )
365+ } else {
366+ s .debugLog ("server: gracefully shutting down" )
367+ }
337368
338369 // 1. Tell amqp we want to shut down by canceling all the consumers.
339370 err = cancelConsumers (inputCh , consumerTags )
340371 if err != nil {
341- return err
372+ return shouldRestart , err
342373 }
343374
344375 // 3. We've told amqp to stop delivering messages, now we wait for all
@@ -355,7 +386,7 @@ func (s *Server) listenAndServe() error {
355386 // 5. We have no more messages incoming and we've published all our
356387 // responses. The closing of connections and channels are deferred so we can
357388 // just return now.
358- return nil
389+ return shouldRestart , nil
359390}
360391
361392func (s * Server ) startConsumers (inputCh * amqp.Channel , wg * sync.WaitGroup ) ([]string , error ) {
@@ -499,6 +530,29 @@ func (s *Server) Stop() {
499530 close (s .stopChan )
500531}
501532
533+ // Restart will gracefully disconnect from AMQP exactly like `Stop` but instead
534+ // of returning from `ListenAndServe` it will set everything up again from
535+ // scratch and start listening again. This can be useful if a server restart is
536+ // wanted without running `ListenAndServe` in a loop.
537+ func (s * Server ) Restart () {
538+ // Restart is noop if not running.
539+ if atomic .LoadInt32 (& s .isRunning ) == 0 {
540+ return
541+ }
542+
543+ // Ensure we never block on the restartChan, if we're in the middle of a
544+ // setup or teardown process we won't be listening on this channel and if so
545+ // we do a noop.
546+ // This can likely happen e.g. if you have multiple messages in memory and
547+ // acknowledging them stops working, you might call `Restart` on all of them
548+ // but only the first one should trigger the restart.
549+ select {
550+ case s .restartChan <- struct {}{}:
551+ default :
552+ s .debugLog ("server: no listener on restartChan, ensure server is running" )
553+ }
554+ }
555+
502556// cancelConsumers will cancel the specified consumers.
503557func cancelConsumers (channel * amqp.Channel , consumerTags []string ) error {
504558 for _ , consumerTag := range consumerTags {
0 commit comments