Skip to content

Commit

Permalink
net/http: add Server.Close & Server.Shutdown for forced & graceful sh…
Browse files Browse the repository at this point in the history
…utdown

Also updates x/net/http2 to git rev 541150 for:

   http2: add support for graceful shutdown of Server
   https://golang.org/cl/32412

   http2: make http2.Server access http1's Server via an interface check
   https://golang.org/cl/32417

Fixes #4674
Fixes #9478

Change-Id: I8021a18dee0ef2fe3946ac1776d2b10d3d429052
Reviewed-on: https://go-review.googlesource.com/32329
Run-TryBot: Brad Fitzpatrick <[email protected]>
Reviewed-by: Brad Fitzpatrick <[email protected]>
  • Loading branch information
bradfitz committed Nov 1, 2016
1 parent 2d4d22a commit 53fc330
Show file tree
Hide file tree
Showing 5 changed files with 352 additions and 15 deletions.
6 changes: 6 additions & 0 deletions src/net/http/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ func (t *Transport) IdleConnKeysForTesting() (keys []string) {
return
}

func (t *Transport) IdleConnKeyCountForTesting() int {
t.idleMu.Lock()
defer t.idleMu.Unlock()
return len(t.idleConn)
}

func (t *Transport) IdleConnStrsForTesting() []string {
var ret []string
t.idleMu.Lock()
Expand Down
62 changes: 52 additions & 10 deletions src/net/http/h2_bundle.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/net/http/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,13 @@ import (
"os/exec"
"reflect"
"testing"
"time"
)

func init() {
shutdownPollInterval = 5 * time.Millisecond
}

func TestForeachHeaderElement(t *testing.T) {
tests := []struct {
in string
Expand Down
93 changes: 93 additions & 0 deletions src/net/http/serve_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4832,3 +4832,96 @@ func TestServerIdleTimeout(t *testing.T) {
t.Fatal("copy byte succeeded; want err")
}
}

func get(t *testing.T, c *Client, url string) string {
res, err := c.Get(url)
if err != nil {
t.Fatal(err)
}
defer res.Body.Close()
slurp, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Fatal(err)
}
return string(slurp)
}

// Tests that calls to Server.SetKeepAlivesEnabled(false) closes any
// currently-open connections.
func TestServerSetKeepAlivesEnabledClosesConns(t *testing.T) {
if runtime.GOOS == "nacl" {
t.Skip("skipping on nacl; see golang.org/issue/17695")
}
defer afterTest(t)
ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
io.WriteString(w, r.RemoteAddr)
}))
defer ts.Close()

tr := &Transport{}
defer tr.CloseIdleConnections()
c := &Client{Transport: tr}

get := func() string { return get(t, c, ts.URL) }

a1, a2 := get(), get()
if a1 != a2 {
t.Fatal("expected first two requests on same connection")
}
var idle0 int
if !waitCondition(2*time.Second, 10*time.Millisecond, func() bool {
idle0 = tr.IdleConnKeyCountForTesting()
return idle0 == 1
}) {
t.Fatalf("idle count before SetKeepAlivesEnabled called = %v; want 1", idle0)
}

ts.Config.SetKeepAlivesEnabled(false)

var idle1 int
if !waitCondition(2*time.Second, 10*time.Millisecond, func() bool {
idle1 = tr.IdleConnKeyCountForTesting()
return idle1 == 0
}) {
t.Fatalf("idle count after SetKeepAlivesEnabled called = %v; want 0", idle1)
}

a3 := get()
if a3 == a2 {
t.Fatal("expected third request on new connection")
}
}

func TestServerShutdown_h1(t *testing.T) { testServerShutdown(t, h1Mode) }
func TestServerShutdown_h2(t *testing.T) { testServerShutdown(t, h2Mode) }

func testServerShutdown(t *testing.T, h2 bool) {
defer afterTest(t)
var doShutdown func() // set later
var shutdownRes = make(chan error, 1)
cst := newClientServerTest(t, h2, HandlerFunc(func(w ResponseWriter, r *Request) {
go doShutdown()
// Shutdown is graceful, so it should not interrupt
// this in-flight response. Add a tiny sleep here to
// increase the odds of a failure if shutdown has
// bugs.
time.Sleep(20 * time.Millisecond)
io.WriteString(w, r.RemoteAddr)
}))
defer cst.close()

doShutdown = func() {
shutdownRes <- cst.ts.Config.Shutdown(context.Background())
}
get(t, cst.c, cst.ts.URL) // calls t.Fail on failure

if err := <-shutdownRes; err != nil {
t.Fatalf("Shutdown: %v", err)
}

res, err := cst.c.Get(cst.ts.URL)
if err == nil {
res.Body.Close()
t.Fatal("second request should fail. server should be shut down")
}
}
Loading

0 comments on commit 53fc330

Please sign in to comment.