505
app.logger.Debug("servers shutting down with eternal grace period")
506
}
507
508
- // shut down servers
509
- for _, server := range app.Servers {
508
+ // goroutines aren't guaranteed to be scheduled right away,
509
+ // so we'll use one WaitGroup to wait for all the goroutines
510
+ // to start their server shutdowns, and another to wait for
511
+ // them to finish; we'll always block for them to start so
512
+ // that when we return the caller can be confident* that the
513
+ // old servers are no longer accepting new connections
514
+ // (* the scheduler might still pause them right before
515
+ // calling Shutdown(), but it's unlikely)
516
+ var startedShutdown, finishedShutdown sync.WaitGroup
517
+
518
+ // these will run in goroutines
519
+ stopServer := func(server *Server) {
520
+ defer finishedShutdown.Done()
521
+ startedShutdown.Done()
522
+
523
if err := server.server.Shutdown(ctx); err != nil {
524
app.logger.Error("server shutdown",
525
zap.Error(err),
526
zap.Strings("addresses", server.Listen))
527
}
528
+ }
529
+ stopH3Server := func(server *Server) {
530
+ defer finishedShutdown.Done()
531
+ startedShutdown.Done()
532
516
- if server.h3server != nil {
517
- // TODO: CloseGracefully, once implemented upstream (see https://github.com/lucas-clemente/quic-go/issues/2103)
518
- if err := server.h3server.Close(); err != nil {
519
- app.logger.Error("HTTP/3 server shutdown",
520
- zap.Error(err),
521
- zap.Strings("addresses", server.Listen))
522
- }
533
+ if server.h3server == nil {
534
+ return
535
+ }
536
+ // TODO: CloseGracefully, once implemented upstream (see https://github.com/lucas-clemente/quic-go/issues/2103)
537
+ if err := server.h3server.Close(); err != nil {
538
+ app.logger.Error("HTTP/3 server shutdown",
539
+ zap.Error(err),
540
+ zap.Strings("addresses", server.Listen))
541
}
542
}
543
544
+ for _, server := range app.Servers {
545
+ startedShutdown.Add(2)
546
+ finishedShutdown.Add(2)
547
+ go stopServer(server)
548
+ go stopH3Server(server)
549
+ }
550
+
551
+ // block until all the goroutines have been run by the scheduler;
552
+ // this means that they have likely called Shutdown() by now
553
+ startedShutdown.Wait()
554
+
555
+ // if the process is exiting, we need to block here and wait
556
+ // for the grace periods to complete, otherwise the process will
557
+ // terminate before the servers are finished shutting down; but
558
+ // we don't really need to wait for the grace period to finish
559
+ // if the process isn't exiting (but note that frequent config
560
+ // reloads with long grace periods for a sustained length of time
561
+ // may deplete resources)
562
+ if caddy.Exiting() {
563
+ finishedShutdown.Wait()
564
+ }
565
+
566
return nil
567
}
568