Should handle shutdown signals in app main file or in actual HTTP server file for graceful shutdown?

I am trying to find out if there is any difference between two implementations that currently do same thing - gracefully shutdown application/server, for example when Ctrl+C is hit. Both works fine and are based on the documentation.

What a friend of mine says is that, the example 2 handles shutdown at application level which shuts down all contexts throughout the application. However, the example 1 does it in HTTP server level which doesn’t necessarily shut down all contexts throughout the application. Since I am a beginner I cannot argue back and need your input on this please.

Example 1

The signals are handled in http.go file so the whole graceful shutdown has been handled in a single file.

cmd/app/main.go

    package main
    
    import (
    	"context"
    	"internal/http"
    	"os"
    	"os/signal"
    	"syscall"
    )
    
    func main() {
    	// Bootstrap config, logger, etc
    
    	http.Start()
    }

internal/http/server.go

    package http
    
    import (
    	"context"
    	"github.com/prometheus/common/log"
    	"net/http"
    	"os"
    	"os/signal"
    	"syscall"
    	"time"
    )
    
    func Start() {
    	log.Infof("starting HTTP server")
    
    	srv := &http.Server{Addr: ":8080", Handler: nil}
    
    	idle := make(chan struct{})
    
    	go shutdown(srv, idle)
    
    	if err := srv.ListenAndServe(); err != http.ErrServerClosed {
    		log.Fatalf("failed to start/close HTTP server [%v]", err)
    	}
    
    	<-idle
    
    	log.Info("gracefully shutdown HTTP server")
    }
    
    func shutdown(srv *http.Server, idle chan<- struct{}) {
    	sig := make(chan os.Signal, 1)
    
    	signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
    
    	<-sig
    
    	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(10)*time.Second)
    	defer cancel()
    
    	if err := srv.Shutdown(ctx); err != nil {
    		log.Fatalf("shutdown HTTP server by interrupting idle connections [%v]", err)
    	}
    
    	close(idle)
    }

Example 2

The signals are handled in application’s main.go file so the whole graceful shutdown has been split within two files. The only addition is this example uses WithCancel context.

cmd/app/main.go

    package main
    
    import (
    	"context"
    	"internal/http"
    	"os"
    	"os/signal"
    	"syscall"
    )
    
    func main() {
    	// Bootstrap config, logger, etc
    
    	backgroundCtx := context.Background()
    	withCancelCtx, cancel := context.WithCancel(backgroundCtx)
    	go shutdown(cancel)
    
    	http.Start(withCancelCtx)
    }
    
    func shutdown(cancel context.CancelFunc) {
    	sig := make(chan os.Signal, 1)
    
    	signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
    
    	<-sig
    
    	cancel()
    }

internal/http/server.go

    package http
    
    import (
    	"context"
    	"github.com/prometheus/common/log"
    	"net/http"
    	"time"
    )
    
    func Start(ctx context.Context) {
    	log.Infof("starting HTTP server")
    
    	srv := &http.Server{Addr: ":8080", Handler: nil}
    
    	idle := make(chan struct{})
    
    	go shutdown(ctx, srv, idle)
    
    	if err := srv.ListenAndServe(); err != http.ErrServerClosed {
    		log.Fatalf("failed to start/close HTTP server [%v]", err)
    	}
    
    	<-idle
    
    	log.Info("gracefully shutdown HTTP server")
    }
    
    func shutdown(ctx context.Context, srv *http.Server, idle chan struct{}) {
    	<-ctx.Done()
    
    	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(10)*time.Second)
    	defer cancel()
    
    	if err := srv.Shutdown(ctx); err != nil {
    		log.Fatalf("shutdown HTTP server by interrupting idle connections [%v]", err)
    	}
    
    	close(idle)
    }
2 Likes

It is look like the same just a design decision. If internal/http is your internal package then there isn`t difference.

2 Likes

Hi @heatbr ,

Thanks for the input. Would you please mind explaining more what exactly you mean and if it was you, which one would you go with?

Thanks

2 Likes

I would without hesitation go with the first one because it is simpler. It’s much easier to understand the logic and what is being done from the code. Only 2 methods and groups of instructions are used, and only one context.

2 Likes

@Christophe_Meessen Thanks for the reply.

So the most important part for me is that to understand if having context.WithCancel is unnecessary or not. Sounds like it is unnecessary. Am I getting it right? I mean if the context.WithTimeout times out, every single context throughout the application would be stopped as well?

2 Likes

This depends on your application. The cancel context can be used to notify other components of your program that a graceful shutdown is performed.

3 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.