在一次平平无奇的下午,望着前面的代码,突然嗅到了 bug 的味道。

func main() {
    f, err := pkg.Open()
    if err != nil {
    	log.Fatalf("xxx open failed. Err: %+v", err)
    }
    defer func() {
        log.Info("start to clean...")
        if err := f.Close(); err != nil {
            log.Fatalf("close xxx failed. Err: %+v", err)
        }
    }()
    for {
        // do something
    }
}

如上所示,主函数进入死循环处理业务后,如果程序退出后就会执行 defer 完成 clean 动作。但是真的会这样吗?

一般我们退出这类程序,常见的有两种方式。开发时的 Ctrl + C 以及托管服务的 service/systemctl 等,这时系统会向程序发送 SIGINT 或者 SIGTERM 信号,程序默认执行 os.Exit(1) 退出。

再来看看 defer,它会在 函数 return, panic, 程序正常退出 之前运行,并不包含 os.Exit 。Exit 函数的描述是这样的,最后一句话强调,会立刻退出不会执行 defer 。

Exit causes the current program to exit with the given status code. Conventionally, code zero indicates success, non-zero an error. The program terminates immediately; deferred functions are not run.

所以上述代码的结果就是结束后并不会执行清理动作,看来还是得对症下药啊。

func main() {
    f, err := pkg.Open()
    if err != nil {
    	log.Fatalf("xxx open failed. Err: %+v", err)
    }
    sig := make(chan os.Signal, 1)
    signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
    go func() {
        <-sig
        log.Info("start to clean...")
        if err := f.Close(); err != nil {
            log.Fatalf("close xxx failed. Err: %+v", err)
        }
        os.Exit(1)
    }()
    
    for {
        // do something
    }
}

one more thing, 可别捕捉到信号忘记退出哦(特殊处理除外