I was referred to this question: Program recovered from panic does not exit as expected
It works fine but it relies on knowing where the panic occurs in order to place the deferred function.
My code is as follows.
package main
import "fmt"
func main() {
defer recoverPanic()
f1()
f2()
f3()
}
func f1() {
fmt.Println("f1")
}
func f2() {
defer f3() //<--- don't want to defer f3 here because I might not know f2 will panic, panic could occuer elsewhere
fmt.Println("f2")
panic("f2")
}
func f3() {
fmt.Println("f3")
}
func recoverPanic() {
if r := recover(); r != nil {
fmt.Printf("Cause of panic ==>> %q\n", r)
}
}
Having the deferred function call f3() in the panicking function works, output below.
f1
f2
f3
Cause of panic ==>> "f2"
What if you have an application where you don't know where a panic occurs, do I need to put a defer in every function that might panic?
Commenting out the defer f3() gives me the following output.
f1
f2
Cause of panic ==>> "f2"
f3 never runs.
My question is how to continue execution of the program without having a deferred function call in every function that might panic?
You can't resume function execution after a panic. Panic is used when the current line of execution cannot continue correctly. Arbitrarily resuming execution after a panic (if it were possible) is begging immediately for another panic, because the state is already incorrect and just blazing ahead won't fix that.
For example, let's say a function panics when it tries to read out of bounds on a slice. How can it continue? What would it even mean to continue? Should it just read the out of bounds memory location and get garbage data? Continue with a zero value? Take a different value from the slice?
You must handle error cases; either by explicitly recovering, or preemptively checking / correcting conditions that will result in panic. At least in the standard library, functions that may spur a panic will say so in their documentation with an explanation of which conditions will result in panic.
If you commonly need to safely call void functions and recover from any panics, you can make a simple wrapper function for that.
func try(f func()) {
defer func() {
if err := recover(); err != nil {
fmt.Println("caught panic:", err)
}
}()
f()
}
Then
func main() {
try(f1)
try(f2)
try(f3)
}
Related
I’m trying to catch crashes/panics from go routines that are created in my program, in order to send them to my crash-error-reporting server (such as Sentry/Raygun)
For example,
func main() {
go func() {
// Get this panic
panic("Go routine panic")
}()
}
The answer states a goroutine cannot recover from a panic in another goroutine.
What would be the idiomatic way to go about it?
You have to "inject" some code into the function that is launched as a new goroutine: you have to call a deferred function in which you call recover(). This is the only way to recover from a panicing state. See related: Why does `defer recover()` not catch panics?
For example:
go func() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Caught:", r)
}
}()
panic("catch me")
}()
This will output (try it on the Go Playground):
Caught: catch me
It is unfeasible to do this in every goroutine you launch, but of course you can move the recovering-logging functionality to a named function, and just call that (but deferred of course):
func main() {
go func() {
defer logger()
panic("catch me")
}()
time.Sleep(time.Second)
}
func logger() {
if r := recover(); r != nil {
fmt.Println("Caught:", r)
}
}
This will output the same (try it on the Go Playground).
Yet another, more convenient and even more compact solution is to create a utility function, a "wrapper" which receives the function, and takes care of the recovering.
This is how it could look like:
func wrap(f func()) {
defer func() {
if r := recover(); r != nil {
fmt.Println("Caught:", r)
}
}()
f()
}
And now using it is even simpler:
go wrap(func() {
panic("catch me")
})
go wrap(func() {
panic("catch me too")
})
It will output (try it on the Go Playground):
Caught: catch me
Caught: catch me too
Final note:
Note that launching an actual goroutine happens outside of wrap(). This gives the caller the option to decide if a new goroutine is required just by prefixing the wrap() call with go. Usually this approach is preferred in Go. This allows you to execute arbitrary functions by passing them to wrap(), and it will "protect" its execution (by recovering from panics, properly logging / reporting it) even if you do not wish to run it concurrently in a new goroutine. On the other hand if you'd move go inside wrap() it wouldn't even work anymore as the recover() call would not happen on the panicking goroutine.
I used to think the panic in a goroutine will kill the program if its caller finishes before the panic (the deferred recovering gives no help since at that point there's no panic occurs yet),
until I tried following code:
func fun1() {
fmt.Println("fun1 started")
defer func() {
if err := recover(); err != nil {
fmt.Println("recover in func1")
}
}()
go fun2()
time.Sleep(10 * time.Second) // wait for the boom!
fmt.Println("fun1 ended")
}
func fun2() {
fmt.Println("fun2 started")
time.Sleep(5 * time.Second)
panic("fun2 booom!")
fmt.Println("fun2 ended")
}
I found no matter the caller function finishes or not, if the goroutines it starts panic, the caller's deferred recover mechanism will not help. The whole program is still dead.
So, WHY? Theoretically the caller function is still running. When the panics happen the caller's deferred functions should work (including the recovering).
The specification says:
While executing a function F, an explicit call to panic or a run-time panic terminates the execution of F. Any functions deferred by F are then executed as usual. Next, any deferred functions run by F's caller are run, and so on up to any deferred by the top-level function in the executing goroutine. At that point, the program is terminated and the error condition is reported, including the value of the argument to panic. This termination sequence is called panicking.
Because fun2 is the top-level function executing in the goroutine and fun2 does not recover from a panic, the program terminates when fun2 panics.
The deferred call in fun1 is not called when the goroutine executing fun2 panics.
A goroutine cannot recover from a panic in another goroutine.
Instead of recovering in fun1() you can use runtime.Goexit() in fun2() which will
Goexit terminates the goroutine that calls it. No other goroutine is
affected.
Something like
func fun2() {
defer func() {
if err := recover(); err != nil {
fmt.Println("Do some cleanup and teardown")
runtime.Goexit() //Here
}
}
...
}
How to obtain the stack trace of the last (ideally of all) go routine (the app has multiple go routines) which panicked and recovered and logged only a not much descriptive error message? I don't know which routine recovered. Also, please keep in mind that I will not alter the code of any imported package. This panic happened in some of the imported packages which creates multiple go routines so I need a way to grab the stack trace of the last recovered routine in order to find where it panic.
The short answer is: Not possible but there are exceptions.
Golang has a few stack control methods and types.
You can control the stack levels with runtime/debug/SetTraceback
func SetTraceback(level string)
SetTraceback sets the amount of detail printed by the runtime
inthe traceback it prints before exiting due to an
unrecovered panic or an internal runtime error.
The level argument takes the same values as the GOTRACEBACK
environment variable. For example, SetTraceback("all") ensure
that the program prints all goroutines when it crashes.
See the package runtime documentation for details. If
SetTraceback is called with a level lower than that of the
environment variable, the call is ignored.
You can also print the stack strace with runtime/debug/Stack
func Stack() []byte
Stack returns a formatted stack trace of the goroutine that calls it. It calls runtime.Stack with a large enough buffer to capture the entire trace.
Also you need to understand how the Built-in funct recover works.
The recover built-in function allows a program to manage behavior of a
panicking goroutine. Executing a call to recover inside a deferred
function (but not any function called by it) stops the panicking sequence
by restoring normal execution and retrieves the error value passed to the
call of panic. If recover is called outside the deferred function it will
not stop a panicking sequence. In this case, or when the goroutine is not
panicking, or if the argument supplied to panic was nil, recover returns
nil. Thus the return value from recover reports whether the goroutine is
panicking.
func recover() interface{}
Working Example
This example assumes that the package does not call recover (detailed in another section).
Golang Playground Link
package main
import (
"log"
"errors"
"runtime/debug"
"time"
)
func f2() {
panic(errors.New("oops")) // line 11
}
func f1() {
f2() // line 15
}
func main() {
defer func() {
if e := recover(); e != nil {
log.Printf("%s: %s", e, debug.Stack()) // line 20
}
}()
go f1() // line 25
time.Sleep(time.Second * 1)
}
If package calls recover
If the code is recovering from the panic you need to use a debugger or remove the recover to understand what is going on as seen on the example below which demonstrate that recovered panics can not be "recovered" again.
Golang Playground Link
package main
import (
"log"
"errors"
"runtime/debug"
"time"
)
func f2() {
panic(errors.New("oops")) // line 11
}
func f1() {
defer func() {
if e := recover(); e != nil {
log.Printf("internal %s: %s", e, debug.Stack()) // line 20
}
}()
f2() // line 15
}
func main() {
defer func() {
if e := recover(); e != nil {
log.Printf("external %s: %s", e, debug.Stack()) // line 20
} else {
log.Println("Nothing to print")
}
}()
go f1() // line 25
time.Sleep(time.Second * 1)
}
Lesser then two evils
Debug with Delve Or temporaly edit the package so it logs the full message (once understood you can revert the changes).
Also if you find the problem let the package author know so it can be fixed.
I was using a library which recover()s from panics, and it was using code that simplifies to the following:
func main() {
defer rec()
panic("X")
}
func rec() {
rec2()
}
func rec2() {
fmt.Printf("recovered: %v\n", recover())
}
The output of this is:
recovered: <nil>
panic: X
... more panic output ...
Notably, recover() returns nil instead of the error. Is this intended behavior?
recover must be called directly by a deferred function.
from the language spec:
The return value of recover is nil if any of the following conditions
holds:
panic's argument was nil;
the goroutine is not panicking;
recover was not called directly by a deferred function.
Why does a call to defer func() { recover() }() successfully recover a panicking goroutine, but a call to defer recover() not?
As an minimalistic example, this code doesn't panic
package main
func main() {
defer func() { recover() }()
panic("panic")
}
However, replacing the anonymous function with recover directly panics
package main
func main() {
defer recover()
panic("panic")
}
Quoting from the documentation of the built-in function recover():
If recover is called outside the deferred function it will not stop a panicking sequence.
In your second case recover() itself is the deferred function, and obviously recover() does not call itself. So this will not stop the panicking sequence.
If recover() would call recover() in itself, it would stop the panicking sequence (but why would it do that?).
Another Interesting Example:
The following code also doesn't panic (try it on the Go Playground):
package main
func main() {
var recover = func() { recover() }
defer recover()
panic("panic")
}
What happens here is we create a recover variable of function type which has a value of an anonymous function calling the built-in recover() function. And we specify calling the value of the recover variable to be the deferred function, so calling the builtin recover() from that stops the panicing sequence.
The Handling panic section mentions that
Two built-in functions, panic and recover, assist in reporting and handling run-time panics
The recover function allows a program to manage behavior of a panicking goroutine.
Suppose a function G defers a function D that calls recover and a panic occurs in a function on the same goroutine in which G is executing.
When the running of deferred functions reaches D, the return value of D's call to recover will be the value passed to the call of panic.
If D returns normally, without starting a new panic, the panicking sequence stops.
That illustrates that recover is meant to be called in a deferred function, not directly.
When it panic, the "deferred function" cannot be the built-in recover() one, but one specified in a defer statement.
DeferStmt = "defer" Expression .
The expression must be a function or method call; it cannot be parenthesized.
Calls of built-in functions are restricted as for expression statements.
With the exception of specific built-in functions, function and method calls and receive operations can appear in statement context.
An observation is that the real problem here is the design of defer and thus the answer should say that.
Motivating this answer, defer currently needs to take exactly one level of nested stack from a lambda, and the runtime uses a particular side effect of this constraint to make a determination on whether recover() returns nil or not.
Here's an example of this:
func b() {
defer func() { if recover() != nil { fmt.Printf("bad") } }()
}
func a() {
defer func() {
b()
if recover() != nil {
fmt.Printf("good")
}
}()
panic("error")
}
The recover() in b() should return nil.
In my opinion, a better choice would have been to say that defer takes a function BODY, or block scope (rather than a function call,) as its argument. At that point, panic and the recover() return value could be tied to a particular stack frame, and any inner stack frame would have a nil pancing context. Thus, it would look like this:
func b() {
defer { if recover() != nil { fmt.Printf("bad") } }
}
func a() {
defer {
b()
if recover() != nil {
fmt.Printf("good")
}
}
panic("error")
}
At this point, it's obvious that a() is in a panicking state, but b() is not, and any side effects like "being in the first stack frame of a deferred lambda" aren't necessary to correctly implement the runtime.
So, going against the grain here: The reason this doesn't work as might be expected, is a mistake in the design of the defer keyword in the go language, that was worked around using non-obvious implementation detail side effects and then codified as such.