Skip to content

Commit 8cc5539

Browse files
committedSep 9, 2023
Fix various request timeout issues
A timeout of 0 should timeout immediately. When retrying requests the timeout should be updated to reflect the already passed time Fixes #1617
1 parent 34e7da1 commit 8cc5539

File tree

2 files changed

+67
-5
lines changed

2 files changed

+67
-5
lines changed
 

‎client.go

+27-5
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,7 @@ func (c *Client) Post(dst []byte, url string, postArgs *Args) (statusCode int, b
380380
// and AcquireResponse in performance-critical code.
381381
func (c *Client) DoTimeout(req *Request, resp *Response, timeout time.Duration) error {
382382
req.timeout = timeout
383-
if req.timeout < 0 {
383+
if req.timeout <= 0 {
384384
return ErrTimeout
385385
}
386386
return c.Do(req, resp)
@@ -412,7 +412,7 @@ func (c *Client) DoTimeout(req *Request, resp *Response, timeout time.Duration)
412412
// and AcquireResponse in performance-critical code.
413413
func (c *Client) DoDeadline(req *Request, resp *Response, deadline time.Time) error {
414414
req.timeout = time.Until(deadline)
415-
if req.timeout < 0 {
415+
if req.timeout <= 0 {
416416
return ErrTimeout
417417
}
418418
return c.Do(req, resp)
@@ -1158,7 +1158,7 @@ func ReleaseResponse(resp *Response) {
11581158
// and AcquireResponse in performance-critical code.
11591159
func (c *HostClient) DoTimeout(req *Request, resp *Response, timeout time.Duration) error {
11601160
req.timeout = timeout
1161-
if req.timeout < 0 {
1161+
if req.timeout <= 0 {
11621162
return ErrTimeout
11631163
}
11641164
return c.Do(req, resp)
@@ -1185,7 +1185,7 @@ func (c *HostClient) DoTimeout(req *Request, resp *Response, timeout time.Durati
11851185
// and AcquireResponse in performance-critical code.
11861186
func (c *HostClient) DoDeadline(req *Request, resp *Response, deadline time.Time) error {
11871187
req.timeout = time.Until(deadline)
1188-
if req.timeout < 0 {
1188+
if req.timeout <= 0 {
11891189
return ErrTimeout
11901190
}
11911191
return c.Do(req, resp)
@@ -1243,8 +1243,27 @@ func (c *HostClient) Do(req *Request, resp *Response) error {
12431243
attempts := 0
12441244
hasBodyStream := req.IsBodyStream()
12451245

1246+
// If a request has a timeout we store the timeout
1247+
// and calculate a deadline so we can keep updating the
1248+
// timeout on each retry.
1249+
deadline := time.Time{}
1250+
timeout := req.timeout
1251+
if timeout > 0 {
1252+
deadline = time.Now().Add(timeout)
1253+
}
1254+
12461255
atomic.AddInt32(&c.pendingRequests, 1)
12471256
for {
1257+
// If the original timeout was set, we need to update
1258+
// the one set on the request to reflect the remaining time.
1259+
if timeout > 0 {
1260+
req.timeout = time.Until(deadline)
1261+
if req.timeout <= 0 {
1262+
err = ErrTimeout
1263+
break
1264+
}
1265+
}
1266+
12481267
retry, err = c.do(req, resp)
12491268
if err == nil || !retry {
12501269
break
@@ -1272,6 +1291,9 @@ func (c *HostClient) Do(req *Request, resp *Response) error {
12721291
}
12731292
atomic.AddInt32(&c.pendingRequests, -1)
12741293

1294+
// Restore the original timeout.
1295+
req.timeout = timeout
1296+
12751297
if err == io.EOF {
12761298
err = ErrConnectionClosed
12771299
}
@@ -2288,7 +2310,7 @@ func (c *pipelineConnClient) DoDeadline(req *Request, resp *Response, deadline t
22882310
c.init()
22892311

22902312
timeout := time.Until(deadline)
2291-
if timeout < 0 {
2313+
if timeout <= 0 {
22922314
return ErrTimeout
22932315
}
22942316

‎client_test.go

+40
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,46 @@ func TestHostClientNegativeTimeout(t *testing.T) {
146146
ln.Close()
147147
}
148148

149+
func TestDoDeadlineRetry(t *testing.T) {
150+
t.Parallel()
151+
152+
tries := 0
153+
done := make(chan struct{})
154+
155+
ln := fasthttputil.NewInmemoryListener()
156+
go func() {
157+
for {
158+
c, err := ln.Accept()
159+
if err != nil {
160+
close(done)
161+
break
162+
}
163+
tries++
164+
br := bufio.NewReader(c)
165+
(&RequestHeader{}).Read(br) //nolint:errcheck
166+
(&Request{}).readBodyStream(br, 0, false, false) //nolint:errcheck
167+
time.Sleep(time.Millisecond * 60)
168+
c.Close()
169+
}
170+
}()
171+
c := &HostClient{
172+
Dial: func(addr string) (net.Conn, error) {
173+
return ln.Dial()
174+
},
175+
}
176+
req := AcquireRequest()
177+
req.Header.SetMethod(MethodGet)
178+
req.SetRequestURI("http://example.com")
179+
if err := c.DoDeadline(req, nil, time.Now().Add(time.Millisecond*100)); err != ErrTimeout {
180+
t.Fatalf("expected ErrTimeout error got: %+v", err)
181+
}
182+
ln.Close()
183+
<-done
184+
if tries != 2 {
185+
t.Fatalf("expected 2 tries got %d", tries)
186+
}
187+
}
188+
149189
func TestPipelineClientIssue832(t *testing.T) {
150190
t.Parallel()
151191

0 commit comments

Comments
 (0)
Please sign in to comment.