125 lines
3.7 KiB
Go
125 lines
3.7 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"sync"
|
|
"syscall"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// Kubernetes stops a pod with SIGTERM; only SIGINT was handled, so a deploy killed the
|
|
// process without running any shutdown code (#50).
|
|
func TestShutdownContext_IsCancelledBySIGTERMAndSIGINT(t *testing.T) {
|
|
for _, sig := range []syscall.Signal{syscall.SIGTERM, syscall.SIGINT} {
|
|
t.Run(sig.String(), func(t *testing.T) {
|
|
ctx, stop := shutdownContext(context.Background())
|
|
defer stop()
|
|
|
|
if err := syscall.Kill(os.Getpid(), sig); err != nil {
|
|
t.Fatalf("send %v: %v", sig, err)
|
|
}
|
|
|
|
select {
|
|
case <-ctx.Done():
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatalf("context not cancelled by %v", sig)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// serveHTTP must not return while a request is in flight or the worker is still recording
|
|
// its job: Serve returns http.ErrServerClosed as soon as Shutdown STARTS, and returning
|
|
// from it let the process exit before either finished (#50). Each half is tested with the
|
|
// other already satisfied, so neither can hold the call open on the other's behalf.
|
|
|
|
func pendingFor(t *testing.T, returned <-chan error, why string) {
|
|
t.Helper()
|
|
select {
|
|
case err := <-returned:
|
|
t.Fatalf("serveHTTP returned (%v) %s", err, why)
|
|
case <-time.After(150 * time.Millisecond):
|
|
}
|
|
}
|
|
|
|
func TestServeHTTP_DrainsAnInFlightRequestBeforeReturning(t *testing.T) {
|
|
release := make(chan struct{})
|
|
entered := make(chan struct{})
|
|
srv := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
close(entered)
|
|
<-release
|
|
_, _ = w.Write([]byte("ok"))
|
|
}))
|
|
srv.Start()
|
|
defer srv.Close()
|
|
var once sync.Once
|
|
releaseHandler := func() { once.Do(func() { close(release) }) }
|
|
defer releaseHandler() // runs before srv.Close: a failing test must not hang on the blocked handler
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
workerDone := make(chan struct{})
|
|
close(workerDone) // the worker is already done: only the request can hold serveHTTP open
|
|
returned := make(chan error, 1)
|
|
go func() { returned <- serveHTTP(ctx, srv.Config, srv.Listener, nil, workerDone, 5*time.Second) }()
|
|
|
|
go func() { _, _ = http.Get(srv.URL) }()
|
|
<-entered
|
|
cancel()
|
|
|
|
pendingFor(t, returned, "while a request was still in flight")
|
|
releaseHandler()
|
|
select {
|
|
case err := <-returned:
|
|
if err != nil {
|
|
t.Fatalf("serveHTTP: %v", err)
|
|
}
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatal("serveHTTP did not return once the request finished")
|
|
}
|
|
}
|
|
|
|
func TestServeHTTP_WaitsForTheWorkerBeforeReturning(t *testing.T) {
|
|
srv := httptest.NewUnstartedServer(http.NotFoundHandler())
|
|
srv.Start()
|
|
defer srv.Close()
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
workerDone := make(chan struct{})
|
|
returned := make(chan error, 1)
|
|
go func() { returned <- serveHTTP(ctx, srv.Config, srv.Listener, nil, workerDone, 5*time.Second) }() // no request in flight
|
|
|
|
cancel()
|
|
|
|
pendingFor(t, returned, "before the worker finished")
|
|
close(workerDone)
|
|
select {
|
|
case err := <-returned:
|
|
if err != nil {
|
|
t.Fatalf("serveHTTP: %v", err)
|
|
}
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatal("serveHTTP did not return once the worker finished")
|
|
}
|
|
}
|
|
|
|
func TestServeHTTP_GivesUpOnAWorkerThatNeverFinishesAfterTheGrace(t *testing.T) {
|
|
srv := httptest.NewUnstartedServer(http.NotFoundHandler())
|
|
srv.Start()
|
|
defer srv.Close()
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
returned := make(chan error, 1)
|
|
go func() {
|
|
returned <- serveHTTP(ctx, srv.Config, srv.Listener, nil, make(chan struct{}), 200*time.Millisecond)
|
|
}()
|
|
|
|
cancel()
|
|
|
|
select {
|
|
case <-returned:
|
|
case <-time.After(3 * time.Second):
|
|
t.Fatal("a stuck worker must not hold the process past the grace period")
|
|
}
|
|
}
|