# Graceful Shutdown — Go Concurrency Patterns

Source: https://www.geekswithgeeks.com/en/go-concurrency/gc-shutdown

> Stop accepting work, finish what is in flight, then exit.

## Signals, contexts and Shutdown

A graceful shutdown has three steps: **notice** the request to stop, **stop accepting** new work, and **drain** in-flight work within a deadline. `signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM)` returns a context that is cancelled when one of those signals arrives (call its `stop` function to restore default signal handling). For HTTP, `srv.Shutdown(ctx)` closes the listeners, closes idle connections and waits for active requests to complete, or returns the context's error if the deadline passes first; at that point `ListenAndServe` returns `http.ErrServerClosed`, which is not a real failure. Background workers should watch the same root context and be tracked with a WaitGroup or errgroup so that `main` waits for them. Shutdown does not wait for hijacked connections such as WebSockets; `RegisterOnShutdown` can notify them.

## An HTTP server and a worker that stop together

SIGINT or SIGTERM triggers a bounded, orderly exit.

```go
package main

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

	"golang.org/x/sync/errgroup"
)

func main() {
	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
	defer stop()

	srv := &http.Server{Addr: ":8080", Handler: http.DefaultServeMux}
	g, gctx := errgroup.WithContext(ctx)

	g.Go(func() error {
		if err := srv.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
			return err
		}
		return nil
	})

	g.Go(func() error { // background worker
		t := time.NewTicker(time.Second)
		defer t.Stop()
		for {
			select {
			case <-gctx.Done():
				return nil
			case <-t.C:
				// periodic job
			}
		}
	})

	g.Go(func() error { // shutdown coordinator
		<-gctx.Done()
		shutCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
		defer cancel()
		return srv.Shutdown(shutCtx) // drain in-flight requests
	})

	if err := g.Wait(); err != nil {
		log.Printf("exit with error: %v", err)
	}
}
```

## Use a fresh context for the drain

The signal context is already cancelled when shutdown starts, so passing it to `Shutdown` would end the drain immediately. Derive the drain deadline from `context.Background()` and keep it shorter than your orchestrator's kill timeout.

**Quiz:** What does http.Server.ListenAndServe return after Shutdown is called?

- [ ] nil
- [x] http.ErrServerClosed
- [ ] context.Canceled
- [ ] It never returns

*Answer:* http.ErrServerClosed. Treat ErrServerClosed as a normal exit, not a failure.
