这是一个历时5年,经过无数次排查才最终解决的BUG,而BUG的原因,却又简单到不可思议。
事情要追溯到2012年,我刚刚做程序猿不到1年,在公司做了一个网站程序,环境是小软件公司常见的Windows+C#+SQLServer微软组合,程序实现了什么功能不重要,某天突然有客户反映「登录不上去,一直提示验证码错误。」这个验证码我做的是随机取两个0-9的数拼个算式,之后生成图片让用户填答案(例如图片生成内容是1+1=?,客户要填2)。答案存在session里,用户提交的时候就把填写内容跟session里面的答案比对。在确定客户不可能一直做错这种最初级算术题的前提下,开始了DEBUG。
然而我没有想到的是,这个看似很小的BUG,却如影随形地跟随了我5年。
1、初次交锋
最初我在测试环境和正式环境下根据客户叙述分别试验,都未发现类似的BUG。尝试远程操作客户电脑,问题重现,排除了客户操作问题。尝试重新获取验证码、强制刷新、清除缓存等方法无效后。受到水平高超的网吧网管的最强解决方案(重启、换机器)启发,使用了【秘技:换个浏览器】,问题解决。
之后断断续续也有其他客户反映同样问题,也都通过此秘技恢复正常使用。但是这个奇怪的BUG原理却完全不明白。使用同样的功能,只有一部分用户会产生这个问题,其他大部分人都是正常的。
2、獠牙初现
好景不长,换浏览器大法使用后没多久,出现了第一批疑难杂症患者:问题又在新的浏览器上重现。
再继续换浏览器终究不是办法,通过各种手段也没排查到问题原因,甚至连公司的一个测试人员也遇到了这个BUG。之前更换其他内核浏览器就好用可能是因为缓存问题,但之前删除过缓存并没有起效。我们抱着玄学的态度,清除了浏览器缓存,然后刷新页面重新登录——登录成功。
原来IE这个功能其实清不干净。
于是我们的解决方案变成了【换成Chrome浏览器,如果还不行,就清缓存】
3、反击
通过各种排查,最终确定BUG来源于session丢失,因为.net的session机制是本地cookies存储了一个「ASP.NET_SessionId」,值与服务器上的sessionid对应,从而确定会话是来自哪台电脑。我当时猜测是因为本地cookies中的sessionid与服务器不对应,导致无法获取会话信息,所以session永远是空的。而清除了cookies之后,重新为客户端生成了新的sessionid,新的id有了对应后,问题解决。
因为在本地调试的用VS自带的IIS Express不会出现这个问题,所以我认为这是服务器上IIS的BUG,但是网上检索相关内容,无论是IIS的这个BUG,还是获取不到session都没有相关结果。
于是我心一横,决定自己写一套session,利用与iis类似的机制,通过客户端cookies中的一个id来与服务端对应,这样直接干掉了IIS的session,就可以不用为这个BUG烦恼了。
编写过程很顺利,新的session通过测试,正式使用。上线后经过几次细节修改,基本完美运行。
与session丢失BUG的战斗也告一段落————本该是这样的。
4、阴魂不散
树欲静而风不止,运行一段时间后,本应完全解决的BUG又卷土重来。与曾经一模一样的情况,所有需要用到session的地方,都是返回空值。与曾经一模一样的随机用户产生、难以复现。
反复排查,却一无所获,这个BUG最大的难点在于我们内部很难复现这个问题,所以十分难以调试,而出现了问题的客户,一般都没法连接到对方电脑操作。只能通过不断地猜测原因,然后修改代码,祈祷这次的改动是有效的,这次的猜测是正确的。
经过了数次修改,效果却并不理想。
5、柳暗花明
2017年的某天,正常工作中,在浏览器控制台中发现了一条异样的cookie信息
UserName=xxxx&Password=xxxx©right=xxxx
这条信息是我设置的自动登录,只要选择自动登录,便将账号和加密后的密码存储到cookies中:
HttpCookie
cookie
=
new
HttpCookie
(
"AutoLogin"
);
cookie
.
Expires
=
DateTime
.
Now
.
AddDays
(
14
);
cookie
.
Values
.
Add
(
"UserName"
,
ul
.
UserName
);
cookie
.
Values
.
Add
(
"Password"
,
ul
.
Password
);
cookie
.
Values
.
Add
(
"copyright"
,
"公司名"
);
Response
.
Cookies
.
Add
(
cookie
);
在控制台里面,copyright是乱码的,因为公司名字是中文,因为没报错所以就没关注过。进一步排查,终于发现了5年前那个BUG的产生原理:
cookies是以文本形式存储在硬盘里,而公司名字乱码恰巧吞掉了字符串结束的限定符,从而使得这个字符串无限延伸,吞掉了后面的cookies。从而在framework获取ASP.NET_SessionId的时候,取不到值,就产生了这个session黑洞。
至于为什么部分用户有这个问题,部分用户没有这个问题……因为大部分用户ASP.NET_SessionId这条是在AutoLogin前面,不会被吞掉,而在后面的,都被吞掉了。
最后删掉各项目中在cookies追加版权信息的代码后,问题解决,从此再也没出现过session黑洞。
最终,一个困扰了我5年的BUG,竟然是这种令人哭笑不得的原因,而耗费了不少心血编写和修改的自定义session组件,也因为经过几年的时间,在各个项目中已经有了非常广泛的应用,修改成本较高。再加上一直正常使用也没出什么问题,就继续用了下去。