一个在 Go 运行时中修复了三年的 bug

全文整理自 golang issue#3250

2012年3月9日:发现

一名开发者在使用 cgo 调用 gtk 时,程序持续报 segment fault,其程序非常简洁:

package main

/*                                                                              
#include <gtk/gtk.h>                                                            
                                                                                
#cgo pkg-config: gtk+-3.0                                                       
*/
import "C"

func main() {
        C.gtk_init(nil, nil)
        C.gtk_file_chooser_button_new(nil, 0)
}

对应程序的 C 版本无此问题:

#include <gtk/gtk.h>

int main(int argc, char **argv)
{
        gtk_init(0, 0);
        gtk_file_chooser_button_new(0, 0);
}

在经过一番简单的背景调查后,rsc(Russ Cox,Go 的核心开发人员)很快就想到了触发场景:通过 cgo 调用的 C 函数创建了私有线程,这些线程继承了 Go 的信号处理函数,而 Go 的信号处理函数并不能与非 Go 线程良好合作。

2012年3月13日:增加诊断日志

rsc 提交了一个简单的 fix,在触发此 bug 时,打印出非 Go 线程接收到具体信号,并认为目前没有能力在 Go 1.0 发布前 fix 此 bug。自此,该 bug 石沉大海。

2013年6月13日:重新提上日程

15个月后,一位名叫 joshrickmar 的开发者遇到了相同问题:使用 cgo 调用 gtk,程序意外退出。

在有了 rsc 的诊断日志后,该开发者明确了能够导致程序崩溃的信号集,并提交了一个 适用于 OpenBSD 平台的 fix,其修复方案为:直接忽略导致程序崩溃的信号集。

2013年7月12日:初步修复

minux(Go 的开发人员)进一步完善 joshrickmar 的修复方案,对于非 Go 线程中 Go 运行时(os/signal.Notify)不关心的信号,直接忽略。并将修复方案适配到了所有已支持平台,完成了对该 bug 的初步修复。

隐患

忽略信号确实解决了使用 cgo 调用 C 函数导致程序崩溃的问题,但于此同时,也带来了隐患。举个例子,程序在非 Go 线程里运行的代码存在 bug,比如写乱内存,正常情况下,程序会因为收到 segment fault 而退出。而直接忽略 SIGSEGV 会导致僵尸线程,实际上,线程已经 crash,却因为 SIGSEGV 被吞掉,不被外界所知。这种 bug 极其隐蔽,会严重推迟问题的暴露时间,造成不可估量的影响,而且还给问题的定位平添麻烦。

2015年7月22日:最终修复

2年后,ianlancetaylor(Go 的开发人员)最终修复了该 bug,其修复方案简单直觉:对于非 Go 线程中 Go 运行时不关心的信号,将信号处理权力归还给用户。

至此,完美地解决了该 bug。

Timeline

2012年3月09日 发现
2012年3月13日 增加诊断日志

2012年3月28日 Go 1.0 发布
2013年5月13日 Go 1.1 发布

2013年6月13日 重新提上日程
2013年7月12日 初步修复

2013年12月01日 Go 1.2 发布
2014年06月18日 Go 1.3 发布
2014年12月10日 Go 1.4 发布

2015年7月22日 最终修复

2015年8月19日 Go 1.5 发布
2016年2月17日 Go 1.6 发布
2016年8月15日 Go 1.7 发布

测试程序

我写了一份测试程序用以验证该 bug 在不同 Go 版本中的表现,我测试了 Go 1.4 和 1.7 两个版本,结论如下:

  1. 对于 Go thread 中触发的 segfault,无论哪个版本都会导致程序 crash。
  2. 对于 C thread 中触发的 segfault
    在 1.4 中,segfault 信号会被悄无声息的吞掉,出错线程僵死,进程不退出
    在 1.7 中,segfault 会导致进程 crash

与此同时,程序中也给出了在低版本 Go 中的修复方案:既然 Go 运行时中的信号处理函数会吞掉其不关注的信号,那么在 C 线程启动后,重置相应信号的处理函数即可。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 179,243评论 25 708
  • 阳光的心态,总是能带来阳光般的好心情。迎着阳光成长,任何负面的情绪都会被阳光蒸发掉。向着太阳成长,无论在哪里都...
    秋日雏菊阅读 466评论 0 1
  • 随着科学的进步,生活水平的提高,养生,已不再只是老年人关注的话题。现在越来越多的年轻人也十分注重养生的,但对于“养...
    ssggou阅读 392评论 0 0
  • input和textarea区别: input是当行文本框,其type值必须是text,初始值是用value或者p...
    石子1110阅读 260评论 0 0
  • 从初中开始,就保持着一个习惯:指甲稍微长一点,就会用指甲钳把指甲剪到接近指甲与肉的接缝。 今天又拿起指甲钳要剪指甲...
    水上车痕阅读 322评论 0 0

友情链接更多精彩内容