Stop gracefully shuts down the HTTP server.
()
| 653 | |
| 654 | // Stop gracefully shuts down the HTTP server. |
| 655 | func (app *App) Stop() error { |
| 656 | ctx := context.Background() |
| 657 | |
| 658 | // see if any listeners in our config will be closing or if they are continuing |
| 659 | // through a reload; because if any are closing, we will enforce shutdown delay |
| 660 | var delay bool |
| 661 | scheduledTime := time.Now().Add(time.Duration(app.ShutdownDelay)) |
| 662 | if app.ShutdownDelay > 0 { |
| 663 | for _, server := range app.Servers { |
| 664 | for _, na := range server.addresses { |
| 665 | for _, addr := range na.Expand() { |
| 666 | if caddy.ListenerUsage(addr.Network, addr.JoinHostPort(0)) < 2 { |
| 667 | app.logger.Debug("listener closing and shutdown delay is configured", zap.String("address", addr.String())) |
| 668 | server.shutdownAt.Store(&scheduledTime) |
| 669 | delay = true |
| 670 | } else { |
| 671 | app.logger.Debug("shutdown delay configured but listener will remain open", zap.String("address", addr.String())) |
| 672 | } |
| 673 | } |
| 674 | } |
| 675 | } |
| 676 | } |
| 677 | |
| 678 | // honor scheduled/delayed shutdown time |
| 679 | if delay { |
| 680 | app.logger.Info("shutdown scheduled", |
| 681 | zap.Duration("delay_duration", time.Duration(app.ShutdownDelay)), |
| 682 | zap.Time("time", scheduledTime)) |
| 683 | time.Sleep(time.Duration(app.ShutdownDelay)) |
| 684 | } |
| 685 | |
| 686 | // enforce grace period if configured |
| 687 | if app.GracePeriod > 0 { |
| 688 | var cancel context.CancelFunc |
| 689 | timeout := time.Duration(app.GracePeriod) |
| 690 | ctx, cancel = context.WithTimeoutCause(ctx, timeout, fmt.Errorf("server graceful shutdown %ds timeout", int(timeout.Seconds()))) |
| 691 | defer cancel() |
| 692 | app.logger.Info("servers shutting down; grace period initiated", zap.Duration("duration", timeout)) |
| 693 | } else { |
| 694 | app.logger.Info("servers shutting down with eternal grace period") |
| 695 | } |
| 696 | |
| 697 | // goroutines aren't guaranteed to be scheduled right away, |
| 698 | // so we'll use one WaitGroup to wait for all the goroutines |
| 699 | // to start their server shutdowns, and another to wait for |
| 700 | // them to finish; we'll always block for them to start so |
| 701 | // that when we return the caller can be confident* that the |
| 702 | // old servers are no longer accepting new connections |
| 703 | // (* the scheduler might still pause them right before |
| 704 | // calling Shutdown(), but it's unlikely) |
| 705 | var startedShutdown, finishedShutdown sync.WaitGroup |
| 706 | |
| 707 | // these will run in goroutines |
| 708 | stopServer := func(server *Server) { |
| 709 | defer finishedShutdown.Done() |
| 710 | startedShutdown.Done() |
| 711 | |
| 712 | // possible if server failed to Start |
no test coverage detected