56 lines
1.6 KiB
Go
56 lines
1.6 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
"time"
|
|
)
|
|
|
|
// shutdownGrace bounds each wait at shutdown (in-flight requests, then the worker). Two of
|
|
// them stay inside Kubernetes' default 30 s termination grace.
|
|
const shutdownGrace = 10 * time.Second
|
|
|
|
// shutdownContext is cancelled by SIGINT (a terminal) or SIGTERM (what Kubernetes sends
|
|
// on every deploy). Handling only SIGINT meant a deploy killed the process before any
|
|
// shutdown code ran (#50).
|
|
func shutdownContext(parent context.Context) (context.Context, context.CancelFunc) {
|
|
return signal.NotifyContext(parent, os.Interrupt, syscall.SIGTERM)
|
|
}
|
|
|
|
// serveHTTP serves srv on ln until it fails or ctx ends, then shuts every server down and
|
|
// waits for in-flight requests and for workerDone (nil: no worker) to finish, each for at
|
|
// most grace. It must not return earlier: Serve returns http.ErrServerClosed as soon as
|
|
// Shutdown STARTS, and returning from main kills the drain and the worker's last write.
|
|
func serveHTTP(ctx context.Context, srv *http.Server, ln net.Listener, extra []*http.Server, workerDone <-chan struct{}, grace time.Duration) error {
|
|
errc := make(chan error, 1)
|
|
go func() { errc <- srv.Serve(ln) }()
|
|
|
|
select {
|
|
case err := <-errc:
|
|
if !errors.Is(err, http.ErrServerClosed) {
|
|
return fmt.Errorf("serve: %w", err)
|
|
}
|
|
return nil
|
|
case <-ctx.Done():
|
|
}
|
|
|
|
shutdownCtx, cancel := context.WithTimeout(context.Background(), grace)
|
|
defer cancel()
|
|
for _, s := range append([]*http.Server{srv}, extra...) {
|
|
_ = s.Shutdown(shutdownCtx)
|
|
}
|
|
if workerDone != nil {
|
|
select {
|
|
case <-workerDone:
|
|
case <-time.After(grace):
|
|
}
|
|
}
|
|
return nil
|
|
}
|