Skip to content

Commit b7f72b4

Browse files
kakkoyunPedram Pourmohammad
andauthored
fix: respect context cancellation in httpClient.Do (#1971)
Co-authored-by: Pedram Pourmohammad <pedram.pourmohamad@gmail.com> Co-authored-by: Kemal Akkoyun <kakkoyun@users.noreply.github.com>
2 parents f23aad5 + da7ec92 commit b7f72b4

2 files changed

Lines changed: 54 additions & 6 deletions

File tree

api/client.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -141,21 +141,21 @@ func (c *httpClient) Do(ctx context.Context, req *http.Request) (*http.Response,
141141
}
142142

143143
var body []byte
144-
done := make(chan error, 1)
144+
done := make(chan struct{})
145+
var readErr error
145146
go func() {
147+
defer close(done)
146148
var buf bytes.Buffer
147-
_, err := buf.ReadFrom(resp.Body)
149+
_, readErr = buf.ReadFrom(resp.Body)
148150
body = buf.Bytes()
149-
done <- err
150151
}()
151152

152153
select {
153154
case <-ctx.Done():
154155
resp.Body.Close()
155-
<-done
156156
return resp, nil, ctx.Err()
157-
case err = <-done:
157+
case <-done:
158158
resp.Body.Close()
159-
return resp, body, err
159+
return resp, body, readErr
160160
}
161161
}

api/client_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,54 @@ func TestDoContextCancellation(t *testing.T) {
164164
}
165165
}
166166

167+
func TestDoContextCancellationSlowServer(t *testing.T) {
168+
serverDone := make(chan struct{})
169+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
170+
w.WriteHeader(http.StatusOK)
171+
if f, ok := w.(http.Flusher); ok {
172+
f.Flush()
173+
}
174+
<-serverDone
175+
}))
176+
defer func() {
177+
close(serverDone)
178+
ts.Close()
179+
}()
180+
181+
client, err := NewClient(Config{
182+
Address: ts.URL,
183+
})
184+
if err != nil {
185+
t.Fatalf("failed to create client: %v", err)
186+
}
187+
188+
req, err := http.NewRequest(http.MethodGet, ts.URL, nil)
189+
if err != nil {
190+
t.Fatalf("failed to create request: %v", err)
191+
}
192+
193+
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
194+
defer cancel()
195+
196+
start := time.Now()
197+
resp, body, err := client.Do(ctx, req)
198+
elapsed := time.Since(start)
199+
200+
if !errors.Is(err, context.DeadlineExceeded) {
201+
t.Errorf("expected error %v, got: %v", context.DeadlineExceeded, err)
202+
}
203+
if body != nil {
204+
t.Errorf("expected no body due to cancellation, got: %q", string(body))
205+
}
206+
if elapsed > 500*time.Millisecond {
207+
t.Errorf("client.Do did not return promptly on cancellation: took %v", elapsed)
208+
}
209+
210+
if resp != nil && resp.Body != nil {
211+
resp.Body.Close()
212+
}
213+
}
214+
167215
// Serve any http request with a response of N KB of spaces.
168216
type serveSpaces struct {
169217
sizeKB int

0 commit comments

Comments
 (0)