diff options
author | Erik Dubbelboer <erik@dubbelboer.com> | 2022-07-29 19:03:15 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-07-29 19:03:15 +0200 |
commit | a5f448fc970972ab47113971d898a22fb28fef52 (patch) | |
tree | 826fe1e91cdca5002f7db0bef4b8dfdcf55efddc /client.go | |
parent | Prevent overflow and panic on large HTTP responses (#1351) (diff) | |
download | fasthttp-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.go | 105 |
1 files changed, 14 insertions, 91 deletions
@@ -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) |