发现了一个关于 gin 1.3.0 框架的 bug
gin 1.3.0 框架 http 响应数据错乱问题排查
问题概述
客户端同时发起多个http请求,gin接受到请求后,其中一个接口响应内容为空,另外一个接口响应内容包含接口1,接口2的响应内容,导致响应数据错乱(偶现问题)
- 图1红框标注部分为正常请求响应
- 图1蓝框标注部分为异常请求响应(可以看到编号2531的响应数据只有一个状态码信息,并没有具体的返回内容)
- 图2 可以看到编号2533的响应数据包含两组object对象信息,其中第一条object信息应该是2531的响应数据
- 图1:

- 图2:

问题分析
因为此问题是偶现问题,有时响应数据又是正常的,而且本次新版本这两个接口代码也并没有修改,所以排查问题花了很长时间,下面是我一步步排查问题的过程.
- 首先怀疑是代码逻辑问题,通过review接口代码逻辑后,这两个接口逻辑都非常简单,且没有任何逻辑关联,所以基本上排除了接口逻辑问题。
- 怀疑是否是并发请求导致的问题,通过golang并且开启多个协程模拟并发发起http请求同时调用这两个接口100次,并没有复现出这种问题,所以可以排除并发请求导致的问题。
- 因为使用golang同时调用这两个接口没有复现此问题,怀疑是否是客户端调用的问题,是否共用了一个http连接发送请求,导致最终响应结果合并到了一起? review客户端代码后,发现代码逻辑也没有什么问题,并且通过抓包后却发现的确是后台响应的数据就有问题,所以可以确认就是后端的问题。
- 此时有同事建议打印一下两个接口后台请求和响应的对象内存地址看一下,是否是共用了同一个对象导致,果然打印之后发现,当数据错乱时,两个接口使用的是同一个对象,两个接口没有任何逻辑关系,为什么会使用同一个请求对象? 为什么就两个接口会出现数据错乱的问题? 难道是gin框架的问题? 此时我们尝试着调试代码去验证
实验验证
- 通过调试发现,调试信息如图3所示(第1部分为正常情况,可以观察到对象指针地址不一样,第2部分为异常情况,可以观察到对象指针地址一样):
- 图3:

此时我观察到每次这两个接口请求后面,都跟着另外一个接口请求,如图1所示的第2494条请求 /api/client/area/scenes 接口,并且本次新版本功能改动了这块的逻辑,会不会是受这个接口的影响了,于是我尝试恢复了这块的代码,恢复后测试多次发现问题无法重现,所以可以断定是受了这块代码的影响.
然而本次修改的代码逻辑主要是为了兼容老版本的客户端,为此接口添加了一个中间件,引入了gin框架的HandleContext(context) 方法,用来做一个统一的中间件,做路由的转发,具体代码逻辑如图4所示.
- 图4:

- gin框架为golang web开发中,很常用的框架,使用人员非常多,这么明显的问题不可能没人发现,虽然极力的认为不可能是框架的问题,但是事实表明就是这里的问题,于是通过查询资料发现,此方法的确可能出现问题,如图5所示
- 图5:

- 可以确认gin框架有问题了,可是原因是什么了?网上并没有详细的说明,于是我打算通过调试阅读源码的方式来测试,在阅读源码的时候我发现,本地代码和gin最新的官方源码已经不一致,于是我发现本地代码版本为1.3.0,而官方代码已经更新到1.6.3了, 如图6所示: 1.6.3已经删除了 engine.pool.Put(c) 此行代码
- 图6:

- 于是我尝试者把gin版本从1.3.0升级到1.6.3,看看问题是否已经解决,果然gin版本升级后,连续测试多次未能重现问题,所以可以确定就是这里的问题,并且问题已经解决
虽然问题已经解决了,但是为什么删除了这一行就可以了? 好像并没有搞清楚具体的原理是什么? 于是我尝试着继续分析原理
- engine.pool.Put(c) 函数使用的是 golang的 sync.Pool 类,sync.Pool设计的目的是用来保存和复用临时对象,以减少内存分配,降低CG压力,Pool对外暴露的主要有三个接口:get(),put(),new()
- Get 返回 Pool 中的任意一个对象。如果 Pool 为空,则调用 New 返回一个新创建的对象。
- sync.Pool 是一个临时对象池。一句话来概括,sync.Pool 管理了一组临时对象,当需要时从池中获取,使用完毕后从再放回池中,以供他人使用。
- Put的过程就是将临时对象放进 Pool 里面。
- 通过如下图7也可以看到 HandleContext 方法上面有一个 ServeHTTP 方法,可以明显看到此方法也调用了 engine.pool.Put(c) 方法,并且也调用了 engine.pool.Get().(Context) 方法,通过调试发现 ServeHTTP 为http请求通用的方法,所有请求都会先调用 ServeHTTP ,如果调用了 HandleContext 则会再调用 HandleContext ,具体执行顺序如下图7所示,如图可以看出来 engine.pool.Put(c) 会执行两次,这样就会导致在sync.Pool存在两个同样的对象,在后面的请求中通过 engine.pool.Get().(Context) 获取context对象时就会获取到相同的context对象,导致ResponseWriter指针一样,从而导致响应数据输出到同一个接口中.
- 图7:

小结
此次问题主要是使用了低版本的gin框架所致,所以可以看出即使再成熟的框架,也可能会存在bug,在实际开发过程中应该及时升级到框架的最新稳定版本, 这样可以防止一些潜在的bug,当发现一些未知的问题,不要凭空猜测,要尽可能的调试代码,找到问题的根本原因.
参考资料:
- gin框架介绍: https://studygolang.com/articles/26000?fr=sidebar
- sync.Pool原理:https://blog.csdn.net/u010853261/article/details/90647884
发现了一个关于 gin 1.3.0 框架的 bug的更多相关文章
- 【编程题目】n 个数字(0,1,…,n-1)形成一个圆圈,从数字 0 开始
第 18 题(数组):题目:n 个数字(0,1,…,n-1)形成一个圆圈,从数字 0 开始,每次从这个圆圈中删除第 m 个数字(第一个为当前数字本身,第二个为当前数字的下一个数字).当一个数字删除后, ...
- for循环练习题(1 ,判断任意一个数是91的多少倍 2,编写程序实现给定一个整数判断它从0到这个整数中间出现多少次9的次数)
1 //判断任意一个数是9的多少倍 #include <stdio.h> #include <stdlib.h> int main() { printf("请输入任意 ...
- javascript随机数发现的一个parseInt函数的问题
前几天想伪造一些数据,用到了随机数,没有自己写,便在网上找了一下,找到了这篇文章:https://www.cnblogs.com/starof/p/4988516.html .之后测试了一下,发现了一 ...
- 排查dubbo接口重复注销问题,我发现了一个巧妙的设计
背景 我在公司内负责自研的dubbo注册中心相关工作,群里经常接到业务方反馈dubbo接口注销报错.经排查,确定是同一个接口调用了两次注销接口导致,由于我们的注册中心注销接口不能重复调用,调用第二次会 ...
- range([start], stop[, step]):产生一个序列,默认从0开始
range([start], stop[, step]):产生一个序列,默认从0开始 >>> l = range(10) >>> l [0, 1, 2, 3, 4, ...
- solaris X86-64下一个ORACLE战斗11.2.0.3.8在一波折叠补丁
solaris X86-64下一个ORACLE战斗11.2.0.3.8补丁: 正确的步骤: 1.BUG6880880 .OPATCH补丁 2.BUG16902043.11.2.0.3.8补丁 情感是练 ...
- VS 2017开发ASP.NET Core Web应用过程中发现的一个重大Bug
今天试着用VS 2017去开发一个.net core项目,想着看看.net core的开发和MVC5开发有什么区别,然后从中发现了一个VS2017的Bug. 首先,我们新建项目,ASP.NET Cor ...
- 发现了一个非常棒的pyqt5的例子集
发现了一个非常棒的pyqt5的例子集 https://github.com/892768447/PyQt 各种各样的PyQt测试和例子 [Python3.4.4 or Python3.5][PyQt5 ...
- Java 读数据库字段时发现的一个现象
早上发现有一个网名叫“帅!是不需要理由”的一个人,在后台只能看到“帅!是不需要理”,“由”字就是不显示出来. 经过分析发现,在Access数据库中,name这个字段的长度是15,因为我知道Access ...
随机推荐
- 《Clojure编程》笔记 第4章 多线程和并发
目录 背景简述 第4章 多线程和并发 4.0 我的问题 4.1 术语 4.1.1 一个必须要先确定的思考基础 4.2 计算在时间和空间内的转换 4.2.1 delay 4.2.2 future 4.2 ...
- [Luogu P2387] [NOI2014]魔法森林 (LCT维护边权)
题面 传送门:https://www.luogu.org/problemnew/show/P2387 Solution 这题的思想挺好的. 对于这种最大值最小类的问题,很自然的可以想到二分答案.很不幸 ...
- socket http1
/*****************************************************************///name : myhttp.h//funct ...
- C/C++四种取整函数floor,ceil,trunc,round
处理浮点数操作常用到取整函数,C/C++提供了四种取整函数 floor函数 floor函数:向下取整函数,或称为向负无穷取整 double floor(double x); floor(-5.5) = ...
- 【SpringBoot】09.SpringBoot整合Freemarker
SpringBoot整合Freemarker 1.修改pom文件,添加坐标freemarker启动器坐标 <project xmlns="http://maven.apache.org ...
- 类似阿里双十一的可视化看板是怎么做的?无人机三维GIS看板也来了!
天猫双十一数据可视化看板 每年的双十一,天猫都会在整点时刻直播战绩,惊叹于可怕战绩的同时,也会被背后展示的数据大屏吸引,这样让人眼前一亮的可视化数据看板是怎么做出来的? 所谓可视化数据看板,就是挂在墙 ...
- VMware 虚拟机下CentOS 7连接网络
查看主机网络配置 ipconfig 1.在centos打开终端命令 2. 输入命令 cd /etc/sysconfig/network-scripts/ 进入文件 输入 ls查看此文件夹下的文件 3 ...
- 大数据分析中数据治理的重要性,从一个BI项目的失败来分析
很多企业在做BI项目时,一开始的目标都是想通过梳理管理逻辑,帮助企业搭建可视化管理模型与深化管理的精细度,及时发现企业经营管理中的问题. 但在项目实施和验收时,BI却变成了报表开发项目,而报表的需求往 ...
- 采用“传统”方式获取当前HttpContext
我们知道"依赖注入"已经成为了.NET Core的基本编程模式,表示当前请求上下文的HttpContext可以通过注入的IHttpContextAccessor服务来提取.有时候我 ...
- 重点思维导图------redis深度历险