项目上遇到一个问题,在json解码接口A返回的数据时,发现竟然解码的是接口B返回的数据,因为项目使用的是fasthttp,怀疑是并发情况下数据被overlap,一查还真是。
fasthttp-repo
响应体数据在并发情况下被overlap的相关issue
相关代码:
http.go的func (resp *Response) Body() []byte函数,在323行左右
// Body returns response body.
//
// The returned value is valid until the response is released,
// either though ReleaseResponse or your request handler returning.
// Do not store references to returned value. Make copies instead.
func (resp *Response) Body() []byte {
if resp.bodyStream != nil {
bodyBuf := resp.bodyBuffer()
bodyBuf.Reset()
_, err := copyZeroAlloc(bodyBuf, resp.bodyStream)
resp.closeBodyStream() //nolint:errcheck
if err != nil {
bodyBuf.SetString(err.Error())
}
}
return resp.bodyBytes()
}
这里的resp.bodyBuffer()从池responseBodyPool取出一个对象A, bodyBuf.Reset()对A里的byte slice做[:0]操作,copyZeroAlloc将resp.bodyStream拷贝到A的byte slice,resp.closeBodyStream()关闭body流,return resp.bodyBytes()将A里的byte slice返回出去。
这时会有一个疑问:对象A不用放回去吗,答案是:要放回去的,在另外一个方法
client.go的func ReleaseResponse(resp *Response)函数,在1095行左右
// ReleaseResponse return resp acquired via AcquireResponse to response pool.
//
// It is forbidden accessing resp and/or its' members after returning
// it to response pool.
func ReleaseResponse(resp *Response) {
resp.Reset()
responsePool.Put(resp)
}
resp.Reset()在http.go里
// Reset clears response contents.
func (resp *Response) Reset() {
resp.Header.Reset()
resp.resetSkipHeader()
resp.SkipBody = false
resp.raddr = nil
resp.laddr = nil
resp.ImmediateHeaderFlush = false
}
func (resp *Response) resetSkipHeader() {
resp.ResetBody()
}
// ResetBody resets response body.
func (resp *Response) ResetBody() {
resp.bodyRaw = nil
resp.closeBodyStream() //nolint:errcheck
if resp.body != nil {
if resp.keepBodyBuffer {
resp.body.Reset()
} else {
responseBodyPool.Put(resp.body)
resp.body = nil
}
}
}
responseBodyPool.Put(resp.body)会将之前取出来的对象A放回去(调用fasthttp.ReleaseResponse方法,特殊例子除外)
,也就是说在将对象A放回去后,在并发情况下对象A有可能被其他请求取出来使用,并往A下的bytes slice里填充数据,将原先的数据overlap掉,项目上使用的情况就是在调用resp.Body()后取得返回值B,在调用fasthttp.ReleaseResponse方法后仍旧使用返回值B,因为B是挂在对象A下的byte slice,其他请求取到A后,往A下的bytes slice里填充数据,将原先的数据overlap掉,结果用B去做json解码时,解码的是其他请求的数据,导致解码失败,程序报错。