当前位置: 首页 > 工具软件 > fasthttp > 使用案例 >

从fasthttp接口返回的响应体数据在并发情况下被overlap的问题排查

李睿
2023-12-01

项目上遇到一个问题,在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解码时,解码的是其他请求的数据,导致解码失败,程序报错。

 类似资料: