每个离谱的bug背后总有一个精彩绝伦的幕后故事
今天看到一个很有意思的bug,背后的故事令人印象深刻。
你知道吗,docker的windows版本不能跟Razer Synapse的驱动管理程序同时运行,至于原因嘛,我们慢慢道来。
这两个程序都希望用户只运行1个程序的实例,这个大家很好理解,同1个程序只打开1次,第2次打开的时候并不打开1个新的窗口,而还是使用之前的打开的程序的实例。
那这个是如何实现的呢?答案是使用它们的.NET assembly 提供的GUID作为全局锁来实现,就像是去上洗手间,每次进去以后把门锁上,这样就可以保证洗手间里一次只有一个人使用了。
那么.NET assembly是什么呢?我对.NET一无所知,翻了下文档,这是官方的解释
An assembly is a collection of types and resources that are built to work together and form a logical unit of functionality. Assemblies take the form of executable (.exe) or dynamic link library (.dll) files, and are the building blocks of .NET application.They provide the common language runtime with the information it needs to be aware of type implementations.
看起来assembly提供了获取程序运行时信息的接口,每个应用程序都有自己独占的assembly。
那么这两个程序是怎么实现获取自身运行时的GUID的呢?下面是代码
string.Format("Global\{0}", (object)
Assembly.GetExecutingAssembly().GetType().GUID);
上面代码的本意是获取正在运行的assembly生成的GUID,这样由于应用程序就可以通过这个唯一的ID去实现全局锁了。这就好比是拿到了应用程序的身份证号码,用这个号码去加锁,相同身份证号的应用程序就不能同时运行了。
想法是好的,然而是上面的实现是大错特错的。
.GetType()方法应该在车底,不应该在车里,这个方法并没有返回assembly本身,它返回的是System.Reflection.RuntimeAssembly,这是.NET的一部分。所以当程序运行的时候,这两个应用程序都会使用GUID加全局锁,但是因为他们并没有使用自己运行时产生的GUID,他们使用的是.NET自身GUID,所以他们用的GUID是一模一样的。这样这对难兄难弟的身份证号是一样的,这样一个人进入了卫生间,用身份证号加了锁,另一个人想进去,哪怕卫生间里有空的隔间,但是由于全局锁用身份证号锁住了,所以管理员是不会让有相同身份证号的人进去的。这两个应用只能运行一个,不能同时运行。
有意思吧,那么为什么这2个应用会同时犯这个低级的错误呢?出错都出的这么心有灵犀?你能猜到吗?
我相信应该有人能猜出来,这是因为stackoverflow的关系。stackoverflow是个程序员问答网站,是程序员版的知乎,不会写的代码去这里问问,很快就会有人给出实现代码。
时间回到2009年有个叫做Nathan的用户问了一个问题:如何获取运行中的assembly的GUID。
12分钟以后,名为Cerebrus的用户回答了这一问题,很不幸,他的回答是错误的。
1年零1个月之后,名为Yoopergeek的用户指出了这一点,Cerebrus给出的获取GUID的方式不正确。此时时钟走到了2010年。
3年后,Cerebrus发现了自己的错误,他回到了这个问题,并修改了自己的答案,不过他并不能删除自己的回答,因为这个答案被标记为了接受,按潜规则,这样的答案是不能被移除的,这是2013年。
5年后的2018年,有用户反馈docker windows版不能与Razer Synapse的驱动管理程序同时运行。
因为这个错误实在过于巧合,所以有人猜测是由于stackoverflow上的错误答案导致了这个可能是史上最有意思bug的产生,也许这两个项目的开发者在09-10年之间不约而同的找到了这个回答,然后没有经过详细验证就把代码拷贝到自己的小本本里。然后命运的车辙终于相交,在后面的一些年月里,这些代码被翻了出来,不约而同的在一些项目中被使用到,从而引发了最终的问题,这段错误的代码终于完成了自己的使命,让这个莫名其妙的bug变得精彩绝伦。
那么终极问题来了:对于测试同学来说,我们如何去有效的发现类似的bug呢?欢迎留言讨论。
最后贴上关于这个问题描述的具体来源,有兴趣的同学可以去考据一下。