Skip to content

Commit

Permalink
net/http: non-keepalive connections close successfully
Browse files Browse the repository at this point in the history
Connections did not close if Request.Close or Response.Close was true. This meant that if the user wanted the connection to close, or if the server requested it via "Connection: close", the connection would not be closed.

Fixes #1967.

R=golang-dev, rsc, bradfitz
CC=golang-dev
https://golang.org/cl/6201044
  • Loading branch information
james4k authored and bradfitz committed May 18, 2012
1 parent 434625d commit ccd63c3
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 2 deletions.
4 changes: 4 additions & 0 deletions src/pkg/net/http/transport.go
Expand Up @@ -599,6 +599,10 @@ func (pc *persistConn) readLoop() {
// before we race and peek on the underlying bufio reader.
if waitForBodyRead != nil {
<-waitForBodyRead
} else if !alive {
// If waitForBodyRead is nil, and we're not alive, we
// must close the connection before we leave the loop.
pc.close()
}
}
}
Expand Down
86 changes: 84 additions & 2 deletions src/pkg/net/http/transport_test.go
Expand Up @@ -13,13 +13,15 @@ import (
"fmt"
"io"
"io/ioutil"
"net"
. "net/http"
"net/http/httptest"
"net/url"
"os"
"runtime"
"strconv"
"strings"
"sync"
"testing"
"time"
)
Expand All @@ -35,6 +37,64 @@ var hostPortHandler = HandlerFunc(func(w ResponseWriter, r *Request) {
w.Write([]byte(r.RemoteAddr))
})

type testCloseConn struct {
net.Conn
set *testConnSet
}

func (conn *testCloseConn) Close() error {
conn.set.remove(conn)
return conn.Conn.Close()
}

type testConnSet struct {
set map[net.Conn]bool
mutex sync.Mutex
}

func (tcs *testConnSet) insert(c net.Conn) {
tcs.mutex.Lock()
defer tcs.mutex.Unlock()
tcs.set[c] = true
}

func (tcs *testConnSet) remove(c net.Conn) {
tcs.mutex.Lock()
defer tcs.mutex.Unlock()
// just change to false, so we have a full set of opened connections
tcs.set[c] = false
}

// some tests use this to manage raw tcp connections for later inspection
func makeTestDial() (*testConnSet, func(n, addr string) (net.Conn, error)) {
connSet := &testConnSet{
set: make(map[net.Conn]bool),
}
dial := func(n, addr string) (net.Conn, error) {
c, err := net.Dial(n, addr)
if err != nil {
return nil, err
}
tc := &testCloseConn{c, connSet}
connSet.insert(tc)
return tc, nil
}
return connSet, dial
}

func (tcs *testConnSet) countClosed() (closed, total int) {
tcs.mutex.Lock()
defer tcs.mutex.Unlock()

total = len(tcs.set)
for _, open := range tcs.set {
if !open {
closed += 1
}
}
return
}

// Two subsequent requests and verify their response is the same.
// The response from the server is our own IP:port
func TestTransportKeepAlives(t *testing.T) {
Expand Down Expand Up @@ -72,8 +132,12 @@ func TestTransportConnectionCloseOnResponse(t *testing.T) {
ts := httptest.NewServer(hostPortHandler)
defer ts.Close()

connSet, testDial := makeTestDial()

for _, connectionClose := range []bool{false, true} {
tr := &Transport{}
tr := &Transport{
Dial: testDial,
}
c := &Client{Transport: tr}

fetch := func(n int) string {
Expand Down Expand Up @@ -107,15 +171,26 @@ func TestTransportConnectionCloseOnResponse(t *testing.T) {
t.Errorf("error in connectionClose=%v. unexpected bodiesDiffer=%v; body1=%q; body2=%q",
connectionClose, bodiesDiffer, body1, body2)
}

tr.CloseIdleConnections()
}

closed, total := connSet.countClosed()
if closed < total {
t.Errorf("%d out of %d tcp connections were not closed", total-closed, total)
}
}

func TestTransportConnectionCloseOnRequest(t *testing.T) {
ts := httptest.NewServer(hostPortHandler)
defer ts.Close()

connSet, testDial := makeTestDial()

for _, connectionClose := range []bool{false, true} {
tr := &Transport{}
tr := &Transport{
Dial: testDial,
}
c := &Client{Transport: tr}

fetch := func(n int) string {
Expand Down Expand Up @@ -149,6 +224,13 @@ func TestTransportConnectionCloseOnRequest(t *testing.T) {
t.Errorf("error in connectionClose=%v. unexpected bodiesDiffer=%v; body1=%q; body2=%q",
connectionClose, bodiesDiffer, body1, body2)
}

tr.CloseIdleConnections()
}

closed, total := connSet.countClosed()
if closed < total {
t.Errorf("%d out of %d tcp connections were not closed", total-closed, total)
}
}

Expand Down

0 comments on commit ccd63c3

Please sign in to comment.