-
Notifications
You must be signed in to change notification settings - Fork 1.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Timeout stream response connection does not clear buffer data for re-use #1743
Comments
Propose solution: resp.bodyStream = newCloseReader(rbs, func() error {
hc.releaseReader(br)
if r, ok := rbs.(*requestStream); ok {
releaseRequestStream(r)
}
if closeConn || resp.ConnectionClose() {
hc.closeConn(cc)
} else {
hc.releaseConn(cc)
}
// if the connection last use time is greater than deadline, close the connection
if cc.lastUseTime.After(deadline) {
hc.closeConn(cc)
}
return nil
}) |
Same, example python upstream: from gevent import monkey; monkey.patch_all()
import gevent
from bottle import route, run
i = 0
@route('/stream')
def stream():
global i
i += 1
yield str(i) + 'part1' + '.' * 30000
gevent.sleep(60)
yield str(i) + 'part2' + '.' * 30000
gevent.sleep(60)
yield str(i) + 'part3' + '.' * 30000
run(host='0.0.0.0', port=8080, server='gevent') Looks like when we re-use
|
* fix: propagate body stream error to close function (#1743) * fix: http test * fix: close body stream with error in encoding functions * fix: lint --------- Co-authored-by: Max Denushev <[email protected]>
* fix: propagate body stream error to close function (#1743) * feat: add address in ErrDialTimeout * feat: add address in any `tryDial` error * feat: use struct to wrap error with upstream info * fix: lint * fix: wrapped Error() method * docs: add example to ErrDialWithUpstream * feat: add address in ErrDialTimeout * feat: add address in any `tryDial` error * feat: use struct to wrap error with upstream info * fix: lint * fix: wrapped Error() method * docs: add example to ErrDialWithUpstream * docs: fix example for ErrDialWithUpstream --------- Co-authored-by: Max Denushev <[email protected]>
I was able to catch the same error on fasthttp v1.55.0. It is kind of api gateway where the server is std http server and the upstream client is fasthttp (I am trying to migrate on it). I am trying to repo the issue but without luck so far. Any hints or ideas on how to repo would be much appreciated. For starters, I am posting snippets of the code where I got the error. I hope it helps. new client: func NewFastClient() *fasthttp.Client {
return &fasthttp.Client{
NoDefaultUserAgentHeader: true,
MaxConnsPerHost: 50000,
MaxIdleConnDuration: time.Second * 50,
ReadTimeout: time.Second * 50,
WriteTimeout: time.Second * 50,
StreamResponseBody: true,
}
} serve (stripped version): fReq := fasthttp.AcquireRequest()
defer fasthttp.ReleaseRequest(fReq)
fReq.Header.DisableSpecialHeader()
fReq.SetRequestURI(targetURL)
fReq.SetBody(ctx.RequestBody)
fReq.Header.SetMethod(method)
// set X-Forwarded-Host to public host name
fReq.Header.Set("X-Forwarded-Host", publicHost)
fReq.Header.Set("Connection", "keep-alive")
fReq.Header.Set("Host", upstreamHost)
fReq.Header.Add("Via", requestProto+" api-gateway")
fResp := fasthttp.AcquireResponse()
defer fasthttp.ReleaseResponse(fResp)
fResp.StreamBody = true
if err := h.httpClient.DoTimeout(fReq, fResp, time.Second*50); isTimeout(err) {
http.Error(downResp, `gateway timeout`, http.StatusGatewayTimeout)
return
} else if err != nil {
l.Error("http client: do failed", logger.Error(err), "err_code", errCode)
//Here I caught the error:
// error when reading response headers: cannot find whitespace in the first line of response
http.Error(downResp, `bad gateway`, http.StatusBadGateway)
return
}
defer func() {
if err := fResp.CloseBodyStream(); err != nil {
l.Error("failed to close response body", logger.Error(err))
}
}()
fResp.Header.VisitAll(func(key, value []byte) {
downResp.Header().Add(string(key), string(value))
})
downResp.WriteHeader(fResp.StatusCode())
if stream := resp.BodyStream(); stream != nil {
if _, err := io.Copy(downResp.Body, resp.BodyStream()); err != nil {
return fmt.Errorf("write response: %w", err)
}
} |
I am using fasthttp as a proxy server which will call downstream service that return stream response, however when downstream request timeout before fully returned the stream response body will cause the next request fail to parse the response header due the reason that the
clientconn
is being re-used and there are some data from previous request.In the default transport
RoundTrip()
, when we only usehc.releaseConn(cc)
in theresp.bodyStream
instead of closing the conn. For stream response, is it possible to close the tcp connection if it is timeout to prevent the dirty data from previous request? Maybe we could change thenewCloseReader
's close function to check if the read is timeout or others issue to better cleanup the connection?How to re-produce:
I tried a dirty hack to always close the
clientconn
when cleaning up theresponse.bodyStream
but not sure if there is a better way of doing it and not really sure if this is a bug or I misuse it?The text was updated successfully, but these errors were encountered: