10

Is it possible to "rethrow" an error from recover and keep the original stack trace? The best I know how to do is to panic again, but that does create a new stacktrace.

func do() {
    defer func() {
        cleanUp()
        if x := recover(); x != nil {
            handleError()
            panic(x)
        }
    }()
    doStuff()
}

My motivation for wanting this is that unless my function exits normally or handleError runs, my program deadlocks. And unless I preserve the original strack trace, I do not know where it crashed.

user7610
  • 25,267
  • 15
  • 124
  • 150
  • Related question https://stackoverflow.com/questions/30062949/golang-checking-for-panic-without-recovering-from-it – user7610 Jan 17 '16 at 18:59

3 Answers3

14

The solution is to not call recover, because then neither rethrowing nor accessing the stack trace is possible. Use a bool flag instead of recover to check for panic.

https://play.golang.org/p/PKeP9s-3tF

func do() {
    panicked := true
    defer func() {
        cleanUp()
        if panicked {
            handleError()
        }
    }()
    doStuff()
    panicked = false
}
user7610
  • 25,267
  • 15
  • 124
  • 150
  • 4
    Note that this approach means you shouldn't return anywhere in `do()` except at the end, below `panicked = false`. Or you'll have to explicitly set `panicked = false` before any other return statement, which is error-prone. Anyone modifying this function later needs to understand this or will regress the logic in the deferred func. I do wish `panic`s could be "re-thrown" as in Java or C# to preserve the original stack trace without resorting to this. – debuggr Apr 18 '18 at 19:55
  • This proposed Go feature will help accomplish it better: [proposal: Go 2 error values #29934](https://github.com/golang/go/issues/29934) – user7610 Feb 07 '19 at 15:33
  • 1
    Is this answer still correct? Looking at the [std library](https://github.com/golang/go/blob/master/src/fmt/scan.go#L253) they "rethrow" the panic after recovering. The problem I have with not recovering is that I can't get access to the underlying error and stack trace without doing so. – Daniel Conde Marin Nov 15 '21 at 10:47
6

Defered functions higher up in the stack will run on panic, even if they don't call recover().

Simply remove the if-statement and the re-panic. Then handle your error, and let the panic continue up the stack.

func do() {
    defer handleError()
    doStuff()
}

a simple demo:

https://play.golang.org/p/UiRou5MhUR

func a() {
    defer func() {
        fmt.Println("a")
    }()
    panic("test")
}
func b() {
    defer func() {
        fmt.Println("b")
    }()
}

func main() {
    fmt.Println("Hello, playground")
    b()
}

outputs

Hello, playground
b
Filip Haglund
  • 13,919
  • 13
  • 64
  • 113
  • 1
    That would work in some cases, but not for me. I call handleError only if there actually is an error. What handleError does is to cause some other gorutines to terminate so that they do not deadlock. I do not want to kill them when there was no error. When there is no error, I do something different. I added a `cleanUp()` call to the question to show what I mean. – user7610 Jan 17 '16 at 21:23
  • 2
    Then you can set a boolean variable, named "succ", initially set to false, and set to true at the end of the function. If succ is set, do not call handleError. – David Jan 18 '16 at 05:21
1

As Daniel Conde Marin questioned here

I do not believe you need to worry about calling panic and passing in a recover()'s response object creating a cryptic or "new" stack trace, at least in go 1.17. It looks like doing so simply adds another "src/runtime/panic.go:<linenum>" frame to the trace.

See this example test: https://goplay.space/#5i1RCJyiFJr

Go does not treat panics like other languages treat try/catch/throw/finally.

user3200607
  • 107
  • 1
  • 2