找bug记(2)

标签: bug | 发表时间:2011-09-02 00:02 | 作者:dennis gengmao
出处:http://www.blogjava.net/killme2008/

    这篇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 201116: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就好。

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

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

dennis 2011-09-02 00:02 发表评论

相关 [bug] 推荐:

找bug记(1)

- BTK 4eVeR - BlogJava-庄周梦蝶
    转载请注明出处 http://www.blogjava.net/killme2008/archive/2011/07/10/354062.html.     上周在线上系统发现了两个bug,值得记录下查找的过程和原因. 以后如果还有查找bug比较有价值的经历,我也会继续分享.     第一个bug的起始,是在线上日志发现一个频繁打印的异常——java.lang.ArrayIndexOutOfBoundsException.

找bug记(2)

- gengmao - BlogJava-庄周梦蝶
    这篇blog迟到了很久,本来是想写另一个跟网络相关bug的查找过程,偷偷懒,写下最近印象比较深刻的bug. 这个bug是我的同事水寒最终定位到的.     前几个月同事报告称有一个线上MQ集群会同一时间抛出ArrayIndexOutOfBoundsException这个异常,也就是数组越界.

Scrum中管理bug

- - CSDN博客研发管理推荐文章
如果bug来自于正在开发的sprint. 会在task阶段就被QA/Scrum Master/Product Owner标记为有bug,并且Story不能被置为done状态,这个很容易解决. 如果bug来自于已经结束的sprint,那么怎么办呢. 理想状态下是将bug放到backlogs中,然后由product owner调整其优先级,并决定放在后面的哪一个sprint中修复.

Fix Bug的五个阶段

- Sirius - 酷壳 - CoolShell.cn
下面的文章和《各种流行的编程方式》有异曲同工,请你不要理解错了. 一个非常严重和困难的bug,能够成就一个饱经沧桑深受压力的有经验的专业程序员的职业生涯. 经受这种考验的创伤程度,相当你受到了一次严重的身体伤害,离婚,或是家庭成为的离世. 研究人员在研究了计算机编程心理学后,得出了一个程序员们在解决一个困难的bug时的心路里程.

mysql order by和limit共用bug

- - 数据库 - ITeye博客
 在mysql下执行没有问题,可以得到预期结果. 但是用jdbc执行的时候就得不到预期结果了. 官网地址:http://bugs.mysql.com/bug.php?id=32933. 以下转载:http://bbs.chinaunix.net/thread-1276235-1-1.html. 我想从一个表中检索所有标题含有“中国”的数据,将它们按id排序,取前5条,所以我写了以下语句.

Java 7被发现含有严重bug

- Jingzhi - Solidot
甲骨文在Java 7中作出某些改变时,可能没很好的进行测试;但在发现严重bug后,它仍按时间表推出了Java 7. Java 7和Java 6都存在相同的bug,不同之处是前者默认启用,而后者没有. HotSpot Loop优化中的bug可能会导致JVM崩溃,或者是执行错误. Bug影响了多个Apache项目,包括Apache Lucene Core和Apache Solr.

Glibc改变导致bug出现

- allengaller - Solidot
在网上通过Flash插件听MP3遭遇破音的Linux用户也许需要去看下Fedora bugzilla,但这个bug的根源颇有些周折: Glibc开发者改动了memcpy()函数实现,它在理论上只是对目前的处理器进行优化,但不幸的是改动暴露了代码中的bug,开发者忽略了传递给memcpy()函数的源和目标数组不能重叠的规定.

首个计算机Bug的由来

- bill - cnBeta.COM
“Bug”一词,是指“故障”、“缺陷”. 了解软件开发的朋友都非常熟悉,程序员和测试人员更不用说,在工作中会常遇到. 9月9日下午在微博上看到@新浪科技发了一条微博消息:.