@@ -8,8 +8,10 @@ package ctxhttp
8
8
9
9
import (
10
10
"io/ioutil"
11
+ "net"
11
12
"net/http"
12
13
"net/http/httptest"
14
+ "sync"
13
15
"testing"
14
16
"time"
15
17
@@ -111,3 +113,64 @@ func doRequest(ctx context.Context) (*http.Response, error) {
111
113
112
114
return Get (ctx , nil , serv .URL )
113
115
}
116
+
117
+ // golang.org/issue/14065
118
+ func TestClosesResponseBodyOnCancel (t * testing.T ) {
119
+ defer func () { testHookContextDoneBeforeHeaders = nop }()
120
+ defer func () { testHookDoReturned = nop }()
121
+ defer func () { testHookDidBodyClose = nop }()
122
+
123
+ ts := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {}))
124
+ defer ts .Close ()
125
+
126
+ ctx , cancel := context .WithCancel (context .Background ())
127
+
128
+ // closed when Do enters select case <-ctx.Done()
129
+ enteredDonePath := make (chan struct {})
130
+
131
+ testHookContextDoneBeforeHeaders = func () {
132
+ close (enteredDonePath )
133
+ }
134
+
135
+ testHookDoReturned = func () {
136
+ // We now have the result (the Flush'd headers) at least,
137
+ // so we can cancel the request.
138
+ cancel ()
139
+
140
+ // But block the client.Do goroutine from sending
141
+ // until Do enters into the <-ctx.Done() path, since
142
+ // otherwise if both channels are readable, select
143
+ // picks a random one.
144
+ <- enteredDonePath
145
+ }
146
+
147
+ sawBodyClose := make (chan struct {})
148
+ testHookDidBodyClose = func () { close (sawBodyClose ) }
149
+
150
+ tr := & http.Transport {}
151
+ defer tr .CloseIdleConnections ()
152
+ c := & http.Client {Transport : tr }
153
+ req , _ := http .NewRequest ("GET" , ts .URL , nil )
154
+ _ , doErr := Do (ctx , c , req )
155
+
156
+ select {
157
+ case <- sawBodyClose :
158
+ case <- time .After (5 * time .Second ):
159
+ t .Fatal ("timeout waiting for body to close" )
160
+ }
161
+
162
+ if doErr != ctx .Err () {
163
+ t .Errorf ("Do error = %v; want %v" , doErr , ctx .Err ())
164
+ }
165
+ }
166
+
167
+ type noteCloseConn struct {
168
+ net.Conn
169
+ onceClose sync.Once
170
+ closefn func ()
171
+ }
172
+
173
+ func (c * noteCloseConn ) Close () error {
174
+ c .onceClose .Do (c .closefn )
175
+ return c .Conn .Close ()
176
+ }
0 commit comments