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

What a friend of mine says, is that Example 2 handles shutdown at the application level which shuts down all contexts throughout the application. However, Example 1 does it at the 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 the 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 := &amp;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)
    }

    &lt;-idle

    log.Info("gracefully shutdown HTTP server")
}

func shutdown(srv *http.Server, idle chan&lt;- struct{}) {
    sig := make(chan os.Signal, 1)

    signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)

    &lt;-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 the application's main.go file so the whole graceful shutdown has been split within two files. The only addition is this example uses the 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)

    &lt;-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 := &amp;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)
    }

    &lt;-idle

    log.Info("gracefully shutdown HTTP server")
}

func shutdown(ctx context.Context, srv *http.Server, idle chan struct{}) {
    &lt;-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)
}
So to be helpful to others who might be interested in the final outcome after many R&amp;D days and suggestions from other forum users so on., this is what I ended up with. I hope it helps and comments are welcome.

cmd/myself/main.go

package main

import (
    "context"
    "net/http"
    "log"
    "os"
    "os/signal"
    "syscall"

    "myself/internal/app"
)

func main() {
    srv := &amp;http.Server{
        Addr:    ":8080",
        Handler: router.New(),
    }

    ctx, cancel := context.WithCancel(context.Background())

    signalChan := make(chan os.Signal, 1)

    go handleSignal(signalChan, cancel)

    if err := app.New(srv).Start(ctx); err != nil {
        log.Fatal(err)
    }

    log.Info("shutdown complete")
}

func handleSignal(signalChan chan os.Signal, cancel context.CancelFunc) {
    // os.Interrupt: Ctrl-C
    // syscall.SIGTERM: kill PID, docker stop, docker down
    signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM)

    sig := &lt;-signalChan

    log.Infof("shutdown started with %v signal", sig)

    cancel()
}


internal/app/mysql.go

package app

import (
    "context"
    "fmt"
    "log"
    "net/http"
    "time"
)

type App struct {
    server  *http.Server
}

func New(srv *http.Server) App {
    return App{
        server:  srv,
    }
}

func (a App) Start(ctx context.Context) error {
    shutdownChan := make(chan struct{})

    go handleShutdown(ctx, shutdownChan, a)

    if err := a.server.ListenAndServe(); err != http.ErrServerClosed {
        return fmt.Errorf("failed to start [%v]", err)
    }

    &lt;-shutdownChan

    return nil
}

func handleShutdown(ctx context.Context, shutdownChan chan&lt;- struct{}, a App) {
    &lt;-ctx.Done()

    ctx, cancel := context.WithTimeout(context.Background(), 10 * time.Second)
    defer cancel()

    if err := a.server.Shutdown(ctx); err != nil {
        log.Infof("interrupted active connections [%v]", err)
    } else {
        log.Infof("served all active connections")
    }

    close(shutdownChan)
}

```