Skip to content

golang 的 defer 与 signal #23

Open

Description

@snxq

在一次平平无奇的下午,望着前面的代码,突然嗅到了 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, 可别捕捉到信号忘记退出哦(特殊处理除外

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions