如何检测延迟函数中的panic(nil)和正常执行Go?

Eon*_*nil 8 go panic

go运行时可以检测panic(nil)并报告错误.

但是,我不能检测panic(nil)recover()defer红色的功能,因为它返回nil,所以我不能正常执行(没有出现恐慌)区别开来,因为我将测试的返回值recover()是零.

例如,

defer func(){
    var err = recover()
    if err != nil {
       // Real serious situation. Panic from inner code.
       // And we may have some critical resources which 
       // must be cleaned-up at any cases.
       // However, this will not be executed for panic(nil) 

       rollback()

       // I am still not sure that how should I treat `panic`…
       // Should I just ignore them?
    }
}()

var err = doTransaction()
if err == nil {
    commit()    // Happy case.
} else {
    rollback()  // Regular execution. Just a lucky case.
}
Run Code Online (Sandbox Code Playgroud)

ROLLBACK只是一个例子,我想我可以有很多关键案例需要清理.好吧,那些清理代码也不会在真正的程序崩溃中执行,但我想尽可能地保护.

如何在延迟函数中检测任何恐慌,无论其参数如何?

Eon*_*nil 5

我只需在退出前设置一个标志即可。

AFAIK,panic 是特定于 Goroutine 的,并且单个 Goroutine 保证在单线程中。因此变量周围不需要同步/锁定ok。如果我错了,请纠正我。

func clean(ok *bool) {
    if *ok {
        log.Printf("Execution OK. No panic detected.\n")
    } else {
        var reason = recover()
        log.Printf("Some bad thing happen. reason = %v\n", reason)
        panic("Abnormal exit. Program abandoned. Stack-trace here.")
        debug.PrintStack() // Oops. this will not run.
    }
}

func main() {
    var ok bool = false

    defer clean(&ok)
    panic(nil)

    test1() // Here's the main job.

    ok = true
    log.Printf("All work done. Quit gracefully.\n")
}
Run Code Online (Sandbox Code Playgroud)


Von*_*onC 5

检查提案 25448 “规范:保证恢复时返回非零值”是否有帮助。

在 Go 1 中允许使用 nil 恐慌值调用恐慌,但很奇怪。

几乎所有代码都通过以下方式检查恐慌:

     defer func() {
        if e := recover(); e != nil { ... }
     }()
Run Code Online (Sandbox Code Playgroud)

...在 的情况下这是不正确的panic(nil)

正确的方法更像是:

     panicked := true
     defer func() {
        if panicked {
              e := recover()
              ...
        }
     }()
     ...
     panicked = false
     return
     ....
     panicked = false
     return
Run Code Online (Sandbox Code Playgroud)

建议:使运行时panic函数将其恐慌值从私有的、不可分配类型的全局值提升nil为类似的runtime.NilPanic值:

package runtime

type nilPanic struct{}

// NilPanic is the value returned by recover when code panics with a nil value.
var NilPanic nilPanic
Run Code Online (Sandbox Code Playgroud)

2018年提出,刚刚被接受(2023年1月)

当前的建议是从 Go 1.21(比如说)开始, panic(nil)变成panic(&runtime.PanicNil{})while panic(因此恰好是接口的变量nil也可以这样做,而不仅仅是文字文本panic(nil))。

panic(nil)总是可以,但在更改后返回而不是recover()返回。 如果,则此更改被禁用并导致像往常一样返回。&runtime.PanicNil{}nil
GODEBUG=panicnil=1panic(nil)recover()nil

假设#56986(“提案:Go 的扩展向后兼容性”)也发生在 Go 1.21 中,这种行为只会在go 1.21工作模块(顶级go.mod)中的“ ”模块中发生变化。
因此,当您从 Go 1.20 更新到 Go 1.21 时,不更改任何go.mod行,您仍然会得到旧的panic(nil)行为。
当您将顶级 go.mod 更改为 时go 1.21,您就会在整个程序中获得新的行为。


thw*_*hwd 4

除非我误解了你的问题,否则即使传递的值是 ,延迟函数调用也会nil在恐慌时运行。下面的程序说明了这一点:

package main

import "fmt"

func main() {
    defer func() {
        fmt.Println("Recover:", recover())
    }()
    panic(nil)
}
Run Code Online (Sandbox Code Playgroud)

因此,您可以通过比较topanic(nil)返回的值轻松检测是否发生。recover()nil

编辑回答评论:

是的,这是真的; 延迟调用通常会在函数返回时运行。但它们也会在panic().

问题更新后编辑:

你是对的,没有办法区分这些情况。另一方面,恐慌也nil没有多大意义——特别是因为这个限制。

我能想到的唯一用例panic(nil)是故意避免恢复并通过堆栈跟踪强制程序崩溃。不过,还有更优雅的方法可以做到这一点,runtime例如使用包。