英文:
How to detect panic(nil) and normal execution in deferred function Go?
问题
Go运行时可以检测到panic(nil)
并报告错误。
然而,我无法通过在defer
函数中使用recover()
来检测到panic(nil)
,因为它返回nil
,所以我无法将其与正常执行(无panic)区分开来,就像我会测试recover()
的返回值是否为nil
一样。
例如,
defer func(){
var err = recover()
if err != nil {
// 真正严重的情况。内部代码引发了panic。
// 并且我们可能有一些必须在任何情况下清理的关键资源。
// 但是对于panic(nil),这段代码将不会被执行。
回滚()
// 我仍然不确定如何处理`panic`...
// 我应该忽略它们吗?
}
}()
var err = doTransaction()
if err == nil {
提交() // 正常情况。
} else {
回滚() // 常规执行。只是幸运的情况。
}
ROLLBACK只是一个示例,我认为我可能有很多需要清理的关键情况。嗯,这些清理代码在真正的程序崩溃时也不会被执行,但我希望尽可能地防御。
如何在延迟函数中检测到任何panic,而不考虑其参数?
英文:
The go runtime can detect panic(nil)
and reports an error.
However, I can't detect panic(nil)
with recover()
in a defer
red function because it returns nil
, so I cannot differentiate it from normal execution (no panic) as I would test for
the return value of recover()
to be nil.
For example,
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 is just an example, and I think I can have plenty of critical cases needs cleanup. Well, those cleanup code won't be executed on real program crash too, but I want to defend as much as possible.
How can I detect any panic regardless of its parameter in a deferred function?
答案1
得分: 5
我只会翻译你提供的内容,以下是翻译结果:
我只是在退出之前设置一个标志。
据我所知,panic是与goroutine相关的,并且单个goroutine保证在单个线程中。因此,在变量ok
周围不需要同步/锁定。如果我错了,请纠正我。
func clean(ok *bool) {
if *ok {
log.Printf("执行正常。未检测到panic。\n")
} else {
var reason = recover()
log.Printf("发生了一些糟糕的事情。原因 = %v\n", reason)
panic("异常退出。程序被放弃。堆栈跟踪在此处。")
debug.PrintStack() // 哎呀,这个不会运行。
}
}
func main() {
var ok bool = false
defer clean(&ok)
panic(nil)
test1() // 这是主要的工作。
ok = true
log.Printf("所有工作已完成。优雅地退出。\n")
}
希望对你有帮助!
英文:
I simply can set a flag before exit.
AFAIK, panic is goroutine-specific, and single goroutine is guaranteed to be in single thread. So synchronization/locking is not required around variable ok
. If I'm wrong, please correct me.
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")
}
答案2
得分: 4
除非我误解了你的问题,否则即使传递的值为nil
,延迟函数调用在发生恐慌时也会运行。这可以通过以下程序进行说明:
package main
import "fmt"
func main() {
defer func() {
fmt.Println("Recover:", recover())
}()
panic(nil)
}
因此,你可以通过将recover()
返回的值与nil
进行比较,轻松检测到是否发生了panic(nil)
。
编辑以回答评论:
是的,没错,延迟调用通常在函数返回时运行。但它们也会在panic()
后展开调用堆栈时运行。
在问题更新后进行编辑:
你说得对,没有办法区分这些情况。另一方面,使用nil
引发恐慌也没有太多意义,特别是因为这个限制。
我能想到的唯一使用panic(nil)
的用例是有意避免恢复并强制程序崩溃并输出堆栈跟踪。不过,有更优雅的方法来实现这一点,例如使用runtime
包。
英文:
Unless I misunderstood your question, deferred function calls will run when panicking, even if the value passed was nil
. This is illustrated by the following program:
package main
import "fmt"
func main() {
defer func() {
fmt.Println("Recover:", recover())
}()
panic(nil)
}
You can therefore easily detect if panic(nil)
happened by comparing the value returned by recover()
to nil
.
Edit to answer comment:
Yes, that's true; deferred calls will normally run when a function returns. But they are also run when unwinding the call-stack after a panic()
.
Edit after question was updated:
You are correct that there is no way to differentiate these cases. On the other hand, panicking with nil
doesn't make much sense either - especially because of this limitation.
The only use-case for panic(nil)
that I could think of would be to deliberately avoid recovery and force the program to crash with a stack trace. There are more elegant ways to do that though, using the runtime
package for instance.
答案3
得分: 2
检查一下提案 25448“spec: 保证从 recover 返回非 nil 值”是否有帮助。
在 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 是当代码以 nil 值 panic 时,由 recover 返回的值。
var NilPanic nilPanic
该提案于 2018 年提出,刚刚在 2023 年 1 月被接受。
当前的提案是,在 Go 1.21(假设)开始,panic(nil)
在 panic
期间会变成 panic(&runtime.PanicNil{})
(因此,一个恰好是 nil
接口的变量也会这样,而不仅仅是字面文本 panic(nil)
)。
在这个改变之后,panic(nil)
仍然是可以的,但是 recover()
返回的是 &runtime.PanicNil{}
而不是 nil
。如果设置了 GODEBUG=panicnil=1
,则禁用此更改,panic(nil)
会导致 recover()
像以前一样返回 nil
。
假设 #56986(“proposal: extended backwards compatibility for Go”)也在 Go 1.21 中实现,那么这种行为只会在模块中发生变化,这些模块在工作模块(顶级 go.mod
)中声明了 go 1.21
。因此,当你从 Go 1.20 更新到 Go 1.21 时,而不更改任何 go.mod
行,你仍然会得到旧的 panic(nil)
行为。当你将顶级 go.mod 更改为 go 1.21
时,整个程序都会采用新的行为。
英文:
Check if the proposal 25448 "spec: guarantee non-nil return value from recover" can help.
>
> Calling panic with a nil panic value is allowed in Go 1, but weird.
>
> Almost all code checks for panics with:
>
> go
> defer func() {
> if e := recover(); e != nil { ... }
> }()
>
> ... which is not correct in the case of panic(nil)
.
>
> The proper way is more like:
>
> go
> panicked := true
> defer func() {
> if panicked {
> e := recover()
> ...
> }
> }()
> ...
> panicked = false
> return
> ....
> panicked = false
> return
>
>
> Proposal: make the runtime panic
function promote its panic value from nil
to something like a runtime.NilPanic
global value of private, unassignable type:
>
> go
> package runtime
>
> type nilPanic struct{}
>
> // NilPanic is the value returned by recover when code panics with a nil value.
> var NilPanic nilPanic
>
Proposed in 2018, it just got accepted (Jan. 2023)
> The current proposal is that starting in Go 1.21 (say),
panic(nil)
turns into panic(&runtime.PanicNil{})
during panic
(so a variable that happens to be a nil
interface does it too, not just the literal text panic(nil)
).
>
>panic(nil)
is always OK, but recover()
returns &runtime.PanicNil{}
instead of nil
after this change.
If GODEBUG=panicnil=1
, then this change is disabled and panic(nil)
causes recover()
to return nil
as it always has.
>
> Assuming #56986 ("proposal: extended backwards compatibility for Go") happens too in Go 1.21, this behavior would only change in modules that say 'go 1.21
' in the work module (top-level go.mod
).
So when you update from Go 1.20 to Go 1.21, without changing any go.mod
lines, you'd still get the old panic(nil)
behavior.
When you change your top-level go.mod to say go 1.21
, then you get the new behavior, in the whole program.
答案4
得分: 1
对于大多数情况来说,if recover() != nil
的惯用方式是有效的,但为了保证健壮性,或者在执行第三方代码时,不应该使用该方式,因为它无法检测到 panic(nil)
。
以下是检查的模式:
func checkPanic(action func()) (panicked bool, panicMsg interface{}) {
panicked = true
defer func() { panicMsg = recover() }()
action()
panicked = false
return
}
如果 action()
发生 panic,那么下面的代码行将不会被执行,panicked
保持为 true。
你可以使用上述辅助函数进行检查,示例如下:
if panicked, msg := checkPanic(func() { /* ... */ }); panicked {
// 处理 panic...
} else {
// 正常情况 :D
}
或者你可以直接将该模式实现到你想要检查的函数中,但这样可能会变得更加混乱。
英文:
For most cases, the idiomatic way of if recover() != nil
works, but for robustness, or when executing third-party code, it should not be used, because it does not detect panic(nil)
.
This pattern is how you can check it:
func checkPanic(action func()) (panicked bool, panicMsg interface{}) {
panicked = true
defer func() { panicMsg = recover() }
action()
panicked = false
return
}
If action()
panics, then the line below is never executed, and panicked
stays true.
You can check it using the above helper function as follows:
if panicked, msg := checkPanic(func() { /* ... */ }); panicked {
// handle panic...
} else {
// happy case :D
}
Or you can directly implement the pattern into the function you want to check, but it might turn out way messier that way.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论