找bug记(2)

  这篇blog迟到了很久,本来是想写另一个跟网络相关bug的查找过程,偷偷懒,写下最近印象比较深刻的bug。这个bug是我的同事水寒最终定位到的。
    前几个月同事报告称有一个线上MQ集群会同一时间抛出ArrayIndexOutOfBoundsException这个异常,也就是数组越界。查看源码,除去一些无关紧要的细节大概是这样子:

public class ConnectionSelector{
    private AtomicInteger sets=new AtomicInteger(0);

   public void selectConnection(List<Connection> connList){
          if(connList==null){
                return null;
           }
          final int size = connList.size();
            if (size == 0) {
                return null;
            }
           return connList.get(sets.incrementAndGet() % size);
}

   }

    很显然,这里的本意是实现一个轮询的连接选择器,返回一个选中的连接。使用AtomicInteger递增并对链表大小取模,返回结果索引位置的连接。异常抛出的位置就是我代码中标红的位置。

    显然,这里有两种可能,一种情况下是说在执行那一行代码的时候,connList的大小缩小了(也就是说连接可能被其他线程移出),那么导致取模的结果越界。另一种可能是取模的结果本身确实超过了列表范围。

    第一种情况是完全可能的,因为服务器的连接可能随时断开或者重连,但是这种情况相对非常少见,因此我们这里并没有对这个选择过程做同步,主要是从性能的角度出发,偶尔的失败可以接受。很遗憾的是,我被我的思维惯性误导了,从来没有怀疑过第二种情况,总是认为是不是真的连接恰巧断开导致这个异常,但是却无法解释这个异常发生后就一直错误下去,无法自行恢复。
    为什么说思维惯性误导呢?这里的问题其实是负数取模的问题,对一个负数进行取模,结果会是正数还是负数?答案是结果因语言而异。
    我很早以前在使用Ruby的时候做过测试,负数取模结果为正数,例如在irb里尝试下:

>> -1000%3
=> 2
>> -2001%4
=> 3

    这个印象持续至今,在clojure里结果也是这样子:

Clojure 1.2.1
user=> (mod -1000 3)
2
user=> (mod -2001 4)
3

    可以再试试python:

Python 2.7.1 (r271:86832, Jun 16 2011, 16:59:05) 
[GCC 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2335.15.00)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> -10000%3
2
>>> -2001%4
3

    这三种语言的结果完全一致,结果都为正数。这个惯性思维延续到java却不成立了,可惜我根本没做测试,让我们试下:

   public static void main(final String[] args) {
        System.out.println(-1000 % 3);
        System.out.println(-2001 % 4);
    }

打印结果为:

-1
-1

    果然,在java里负数取模的结果为负数,而不是我习惯性地认为是正数。因此最终的定位到的原因就是sets这个变量递增超过Integer.MAX_VALUE后越界变成负数了,取模的结果为负数,导致抛出数组越界的异常,这也解释了为什么同一个集群都在同一时间出问题,因为这个集群内的机器启动时间相邻并且调用这个方法次数相对平均。修正问题很简单,加个Math.abs就好。

    Update:加个abs是不够的,因为Math.abs的javadoc提醒了:

Note that if the argument is equal to the value of Integer.MIN_VALUE, the most negative representable int value, the result is that same value, which is negative.

    也就是说对Integer.MIN_VALUE做abs结果仍然是负数。尽管在这个场景中失败一次可以接受,但是最好的办法还是回复中steven提到的抵消符号位的做法:

(sets.incrementAndGet() & 0x7FFFFFFF) % size

   
    这个问题更详细的讨论后来我找到这篇博客,作者讨论几种语言和计算器的这个问题的结果,给出了一些结论。不过我觉的这个结论可能也不是那么可靠,特别是对c/c++来说,很大程度上应该还是依赖于实现,最可靠的办法还是强制结果为正。

    这个bug的几个教训:
1、首先是第一次出现的时候没有引起足够重视,重启解决问题后没有深究。有句玩笑话:99%的程序问题都可以通过重启解决。但是事实上问题仍然存在,该发生的终究还会发生。不管你信不信,它就是发生了,这是一个奇迹。
2、注意大脑的思维惯性,经验主义和教条主义都不可取。最近在读一本好书《暗时间》,大脑误导我们的手段可是多种多样。
3、最后就是这个负数取模的结果因语言而异,不要依赖于特定实现。

文章转自庄周梦蝶  ,原文发布时间 2011-09-02

时间: 2024-11-28 13:54:24

找bug记(2)的相关文章

找bug记(1)

上周在线上系统发现了两个bug,值得记录下查找的过程和原因.以后如果还有查找bug比较有价值的经历,我也会继续分享. 第一个bug的起始,是在线上日志发现一个频繁打印的异常--java.lang.ArrayIndexOutOfBoundsException.但是却没有堆栈,只有一行一行的ArrayIndexOutOfBoundsException.没有堆栈,不知道异常是从什么地方抛出来的,也就不能找到问题的根源,更谈不上解决.题外,工程师在用log4j记录错误异常的时候,我看到很多人这样用(假设

java-什么是bug 怎么找bug

问题描述 什么是bug 怎么找bug 什么是bug 怎么找bug 我是个菜鸟 请大神来解答一下 解决方案 bug 就是系统漏洞 就像玩游戏的时候掉出地图外 就是卡入了bug 解决方案二: 你可以将bug理解成系统的一个小错误,只是这个错误对整个系统的运行没有太大的影响 解决方案三: bug是发生在意想之后的情况,通常是由于逻辑不够严谨或对环境不够了解造成的,找bug这个需要有丰富的经验,因为有的bug很难分辨出来

java程序报错,求大神找bug

问题描述 java程序报错,求大神找bug package arraysofobjects; import java.util.Scanner; public class arraysofobjects { public static void main(String[] args) { Scanner input=new Scanner(System.in); students[] stu=new students[4]; String name; int grade; for(int i=0

字符串排序-该段程序是字符串冒泡排序,请大神找bug,做了好久了

问题描述 该段程序是字符串冒泡排序,请大神找bug,做了好久了 #include #include using namespace std; int c; cin >> c; char b[100][100]; for (int m = 0; m<c; m++){ cin >> b[m]; } int n; for (int j = 0; j < c; j++){ for (int m = 0; m<c ; m++){ if (strlen(b[j])>st

c语言问题-C语言请大神帮忙找BUG

问题描述 C语言请大神帮忙找BUG #include #define MAXIMUM 1000 //这段代码是要实现将一个字符串中的连续的空格用数 int getline(char line[]); //较少的指标符和空格代替,请指点,如其中有什么低等 main(){ //错误也请大神们指点,本人新手只懂皮毛,谢谢. int len; char line[MAXIMUM]; char newline[MAXIMUM]; int j; int k = 0; int ecount; int n; w

javascript-前辈,JavaScript正则匹配,求找bug,谢谢

问题描述 前辈,JavaScript正则匹配,求找bug,谢谢 <body> <script> var objStr="手机号A13512345678,手机号B13212345678,手机号C13912345678"; var reg=new RegExp("13[4-9](//d){8}","g"); document.write("发现移动手机号码"); findPhoneNumbers(objSt

微软领投网络安全公司Synack巨额融资:雇黑客找BUG

据<财富>杂志北京时间4月12日报道,微软创投日前领投了网络安全公司Synack的2125万美元融资,该公司业务模式奇特,雇佣黑客为客户的软件寻找漏洞. 参与这家加州雷德伍德城公司C轮融资的还包括,惠普企业,新加坡电信旗下风投机构Innov8. 新一轮融资让Synack迄今的总融资额增至了5500万美元.该公司此前的投资者包括纪源资本.谷歌风投和凯鹏华盈. Synack的业务模式类似于渗透测试人员的Uber,为高端客户加快它们的私人漏洞赏金项目.该公司管理着一个黑客网络,其中的黑客全部经过审查

BUG现形记(二)——偷工减料的复制构造函数

[课程支撑]我的 C++程序设计课程教学材料 [摘要]设计数组类,要实现数组类中两个数组相加的运算,程序却陷入死循环.逐层排查,重载的加法正确,重载的赋值运算也看不出问题.跟踪到赋值运算的实现中发现,传递的参数中有异常,终于找出了嫌疑犯--编制的复制构造函数偷工减料. [阅读提示]现在打开你熟悉的c++,跟随作者的的思路,重走发现嫌犯的过程. 题目是建立专门的数组类处理有关数组的操作,要完成支持数组操作的类的设计,增强C++内置数组类型功能.--见:第14周-任务1-数组类的构造 有同学向我求助

找出诡异的Bug:数据怎么存不进去

带着学生做课程设计.程序一大,课程中做过了小项目,练过了分解动作,一到合起来了,难免还是要乱了分寸.其实,实战的功夫,就是这样出来的.(课程设计指导视频链接(第36课时,3.18 银行系统开发),课程主页在链接,指导文档见链接,示例程序见链接). 话说,已经有两位做银行系统的同学和我说,"文件中写不进去数据.程序一退出,明明写进去了,结果却是空文件."这不是一个小打击. 做软件,找Bug,有些像打空气,使半天劲,人家就不理你.学计算机的人,练的就是这样的功夫,要学会自己创建线索,找出问