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

ojsjcaue  于 2023-01-15  发布在  Go
关注(0)|答案(4)|浏览(130)

go运行时可以检测panic(nil)并报告错误。
但是,我无法在defer red函数中检测到panic(nil)recover(),因为它返回nil,所以我无法将其与正常执行(无异常)区分开来,因为我将测试recover()的返回值是否为nil。
例如,

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.
}

ROLLBACK只是一个例子,我想我可以有很多关键的情况下需要清理。嗯,这些清理代码不会执行真正的程序崩溃太多,但我想尽可能多的防御。
如何检测延迟函数中的任何异常,而不管其参数是什么?

erhoui1w

erhoui1w1#

除非我误解了您的问题,否则延迟函数调用 * 将 * 在死机时运行,即使传递的值是nil

package main

import "fmt"

func main() {
    defer func() {
        fmt.Println("Recover:", recover())
    }()
    panic(nil)
}

因此,通过比较recover()nil返回的值,您可以轻松地检测panic(nil)是否发生。
编辑以回答注解:
是的,那是真的;延迟调用通常会在函数返回时运行,但在panic()之后展开调用堆栈时也会运行。
更新问题后编辑:
你说的对,没有办法区分这些情况,另一方面,对nil的恐慌也没有多大意义--特别是因为这个限制。
我能想到的panic(nil)的唯一用例是故意避免恢复,并通过堆栈跟踪强制程序崩溃,不过还有更优雅的方法可以做到这一点,例如使用runtime包。

byqmnocz

byqmnocz2#

我可以在退出前设置一个标志。
好吧,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")
}
r8uurelv

r8uurelv3#

大多数情况下,if recover() != nil的惯用方法是有效的,但是为了健壮性,或者在执行第三方代码时,不应该使用它,因为它不检测panic(nil)
下面的模式是您可以检查它的方法:

func checkPanic(action func()) (panicked bool, panicMsg interface{}) {
    panicked = true
    defer func() { panicMsg = recover() }
    action()
    panicked = false
    return
}

如果action()死机,那么下面的行将永远不会执行,panicked将保持为真。您可以使用上面的helper函数进行检查,如下所示:

if panicked, msg := checkPanic(func() { /* ... */ }); panicked {
    // handle panic...
} else {
    // happy case :D
}

或者,您可以直接将模式实现到要检查的函数中,但这样做可能会变得更混乱。

oogrdqng

oogrdqng4#

检查proposal 25448“规格:保证从recover返回非空值”会有所帮助。
在Go语言1中允许使用nil panic值调用panic,但是很奇怪。
几乎所有代码都使用以下命令检查死机:

defer func() {
        if e := recover(); e != nil { ... }
     }()

...这在panic(nil)的情况下是不正确的。
正确的方式更像是:

panicked := true
     defer func() {
        if panicked {
              e := recover()
              ...
        }
     }()
     ...
     panicked = false
     return
     ....
     panicked = false
     return

建议:让运行时panic函数将其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

2018年提出,刚刚获得受理(2023年1月)
目前的建议是,从Go语言1.21开始(比如说),panic(nil)panic期间变成panic(&runtime.PanicNil{})(所以一个恰好是nil接口的变量也会这样做,而不仅仅是文本panic(nil))。
panic(nil)始终为OK,但在此更改之后,recover()返回&runtime.PanicNil{}而不是nil
如果为GODEBUG=panicnil=1,则禁用此更改,并且panic(nil)会使recover()返回nil,因为它始终如此。
假设#56986(“建议:Go语言的扩展向后兼容性”)也发生在Go语言1.21中,这种行为只会在工作模块(顶层go.mod)中的“go 1.21”模块中发生变化。
因此,当您从Go语言1.20升级到Go语言1.21时,如果不更改任何go.mod行,您仍然会得到旧的panic(nil)行为。
当你把你的顶层go.mod修改为go 1.21时,你就会在整个程序中得到新的行为。

相关问题