aboutsummaryrefslogtreecommitdiff
path: root/client.go
diff options
context:
space:
mode:
authorGravatar Erik Dubbelboer <erik@dubbelboer.com> 2022-07-29 19:03:15 +0200
committerGravatar GitHub <noreply@github.com> 2022-07-29 19:03:15 +0200
commita5f448fc970972ab47113971d898a22fb28fef52 (patch)
tree826fe1e91cdca5002f7db0bef4b8dfdcf55efddc /client.go
parentPrevent overflow and panic on large HTTP responses (#1351) (diff)
downloadfasthttp-a5f448fc970972ab47113971d898a22fb28fef52.tar.gz
fasthttp-a5f448fc970972ab47113971d898a22fb28fef52.tar.bz2
fasthttp-a5f448fc970972ab47113971d898a22fb28fef52.zip
Improve Client timeout (#1346)
Don't run requests in a separate Goroutine anymore. Instead use proper conn deadlines to enforce timeouts. - Also contains some linting fixes.
Diffstat (limited to 'client.go')
-rw-r--r--client.go105
1 files changed, 14 insertions, 91 deletions
diff --git a/client.go b/client.go
index 42f0bce..8cf1ecc 100644
--- a/client.go
+++ b/client.go
@@ -387,7 +387,8 @@ func (c *Client) Post(dst []byte, url string, postArgs *Args) (statusCode int, b
// If requests take too long and the connection pool gets filled up please
// try setting a ReadTimeout.
func (c *Client) DoTimeout(req *Request, resp *Response, timeout time.Duration) error {
- return clientDoTimeout(req, resp, timeout, c)
+ req.timeout = timeout
+ return c.Do(req, resp)
}
// DoDeadline performs the given request and waits for response until
@@ -414,7 +415,8 @@ func (c *Client) DoTimeout(req *Request, resp *Response, timeout time.Duration)
// It is recommended obtaining req and resp via AcquireRequest
// and AcquireResponse in performance-critical code.
func (c *Client) DoDeadline(req *Request, resp *Response, deadline time.Time) error {
- return clientDoDeadline(req, resp, deadline, c)
+ req.timeout = time.Until(deadline)
+ return c.Do(req, resp)
}
// DoRedirects performs the given http request and fills the given http response,
@@ -1150,7 +1152,8 @@ func ReleaseResponse(resp *Response) {
// If requests take too long and the connection pool gets filled up please
// try setting a ReadTimeout.
func (c *HostClient) DoTimeout(req *Request, resp *Response, timeout time.Duration) error {
- return clientDoTimeout(req, resp, timeout, c)
+ req.timeout = timeout
+ return c.Do(req, resp)
}
// DoDeadline performs the given request and waits for response until
@@ -1172,7 +1175,8 @@ func (c *HostClient) DoTimeout(req *Request, resp *Response, timeout time.Durati
// It is recommended obtaining req and resp via AcquireRequest
// and AcquireResponse in performance-critical code.
func (c *HostClient) DoDeadline(req *Request, resp *Response, deadline time.Time) error {
- return clientDoDeadline(req, resp, deadline, c)
+ req.timeout = time.Until(deadline)
+ return c.Do(req, resp)
}
// DoRedirects performs the given http request and fills the given http response,
@@ -1199,93 +1203,6 @@ func (c *HostClient) DoRedirects(req *Request, resp *Response, maxRedirectsCount
return err
}
-func clientDoTimeout(req *Request, resp *Response, timeout time.Duration, c clientDoer) error {
- deadline := time.Now().Add(timeout)
- return clientDoDeadline(req, resp, deadline, c)
-}
-
-func clientDoDeadline(req *Request, resp *Response, deadline time.Time, c clientDoer) error {
- timeout := -time.Since(deadline)
- if timeout <= 0 {
- return ErrTimeout
- }
-
- var ch chan error
- chv := errorChPool.Get()
- if chv == nil {
- chv = make(chan error, 1)
- }
- ch = chv.(chan error)
-
- // Make req and resp copies, since on timeout they no longer
- // may be accessed.
- reqCopy := AcquireRequest()
- req.copyToSkipBody(reqCopy)
- swapRequestBody(req, reqCopy)
- respCopy := AcquireResponse()
- if resp != nil {
- // Not calling resp.copyToSkipBody(respCopy) here to avoid
- // unexpected messing with headers
- respCopy.SkipBody = resp.SkipBody
- }
-
- // Note that the request continues execution on ErrTimeout until
- // client-specific ReadTimeout exceeds. This helps limiting load
- // on slow hosts by MaxConns* concurrent requests.
- //
- // Without this 'hack' the load on slow host could exceed MaxConns*
- // concurrent requests, since timed out requests on client side
- // usually continue execution on the host.
-
- var mu sync.Mutex
- var timedout, responded bool
-
- go func() {
- reqCopy.timeout = timeout
- errDo := c.Do(reqCopy, respCopy)
- mu.Lock()
- {
- if !timedout {
- if resp != nil {
- respCopy.copyToSkipBody(resp)
- swapResponseBody(resp, respCopy)
- }
- swapRequestBody(reqCopy, req)
- ch <- errDo
- responded = true
- }
- }
- mu.Unlock()
-
- ReleaseResponse(respCopy)
- ReleaseRequest(reqCopy)
- }()
-
- tc := AcquireTimer(timeout)
- var err error
- select {
- case err = <-ch:
- case <-tc.C:
- mu.Lock()
- {
- if responded {
- err = <-ch
- } else {
- timedout = true
- err = ErrTimeout
- }
- }
- mu.Unlock()
- }
- ReleaseTimer(tc)
-
- errorChPool.Put(chv)
-
- return err
-}
-
-var errorChPool sync.Pool
-
// Do performs the given http request and sets the corresponding response.
//
// Request must contain at least non-zero RequestURI with full url (including
@@ -1464,6 +1381,12 @@ func (c *HostClient) doNonNilReqResp(req *Request, resp *Response) (bool, error)
err = bw.Flush()
}
c.releaseWriter(bw)
+
+ // Return ErrTimeout on any timeout.
+ if x, ok := err.(interface{ Timeout() bool }); ok && x.Timeout() {
+ err = ErrTimeout
+ }
+
isConnRST := isConnectionReset(err)
if err != nil && !isConnRST {
c.closeConn(cc)