6 ms·
I programmed in Go for 5 years (stopped 2 years ago) and didn't even know the language had recover() until 5 minutes ago. I used panic() all day, but never rec
by shric 2y ago
I programmed in Go for 5 years (stopped 2 years ago) and didn't even know the language had recover() until 5 minutes ago.
I used panic() all day, but never recover. I use panic for unrecoverable errors. I thought that's why it's called "panic".
- troupo 2y agoIn most software there's no such thing as unrecoverable panic. OOM is probably the only such error, and even then it doesn't come from within your app. For all "unrecoverable panics" you usually want to see the reason, log it, kill the offending process, clean up resources, and then usually restart the offending process. And that's the reason both Go and Rust ended up reverting their stance on "unrecoverable panics kill your program" and introduced ways to recover from them.
- Ferret7446 2y agoGo never had a stance on "unrecoverable panics kill your program". Go always supported recover, but encourages (correctly IMO) error values because they are more performant and easier to understand. The Go standard library even uses panic/recover (aka throw/catch) style programming in specific instances.
- troupo 2y ago> they are more performant and easier to understand. They are more performant because Go decided to make them so. E.g. in Erlang crashing a process is an expected lightweight operation. As for "easier to understand"... They are not when: - your code is littered with `x, err = ...; if err != nil` - it's not easier to understand when the code errors have to be dealt with on a higher/different level. The calling code isn't always the one that needs to deal with all the errors Just a very random example (I literally just clicked through random files): https://github.com/kubernetes/kubernetes/blob/master/pkg/controlplane/controller/clusterauthenticationtrust/cluster_authentication_trust_controller.go#L143 https://github.com/kubernetes/kubernetes/blob/master/pkg/con... Oh, look, you can't even see the logic behind all the `if err`s which do nothing but return the error to be handled elsewhere.
- zmgsabst 2y agoI always liked Erlangs “crash until someone knows how to recover”.
- CharlieDigital 2y agoYou aren't kidding. Line 143 - 182... You'd think they'd come up with a short form for something that gets written so often.
- skywhopper 2y agoThey haven’t, because it’s perfectly legible, versus languages that hide control flow with implicit assumptions. The main point of Go was to avoid magic. If you don’t care for it, that’s fine. Plenty of us prefer explicit control flow.
- williamdclt 2y agoEveryone prefers explicit control flows. But there's other things in the balance (noise, code density, readability), and many people think that this is pretty bad trade-off. Rust with Optionals strikes a much nicer trade-off, and personally I'd love to see a language that offers a good checked exception mechanism which would offer a lot of explicitness with most noise removed.
- CharlieDigital 2y agoHow about a simple one liner like (just throwing out random ideas; I'm sure these are not idiomatic) return when err return if err return ?> err return ? err Still readable, still pretty explicit IMO.
- vbezhenar 2y ago`return err` is a terrible approach. You must wrap the error with new error, include file and line information, so stacktrace is actually useful. When program spews some random garbage into its stderr and you have no idea where that message came from (imagine looking through millions of lines of kubernetes implementation), that's the good way to ensure that nobody would even care to look at it. Thousands of unsolved issues in kubernetes tracker is a perfect demonstration of it. Go is just bad at handling errors. Exceptions are superior in every way.
- shric 2y agoFor me an unrecoverable error is when my program gets into an unexpected state. Given that I didn't anticipate such a thing ever happening, I can no longer reason about what the program will do, so the only sensible course of action is to crash immediately.
- bilekas 2y agoI have to add the obligatory “you’ve solved the halting problem” question here.
- troupo 2y agoNo idea what this has to do with the halting problem
- bilekas 2y ago> In most software there's no such thing as unrecoverable panic > The halting problem is the problem of determining, from a description of an arbitrary computer program and an input, whether the program will finish running They’re very much related to determining you’re gonna panic or not.
- usrbinbash 2y ago> In most software there's no such thing as unrecoverable panic Webserver wants to start, binding port 443/80 isn't possible because another process holds that port. Logging service wants to write to disk. The IO operation fails. RDBMS want's to access the persistent storage, the syscall fails due to insufficient permissions. How are any of those recoverable?
- troupo 2y agoNote how none of these issues should cause the respective programs to crash, as this is what `panic` does. They try to start, cannot do a specific operation, and they do an orderly shutdown. Or they should
- usrbinbash 2y ago> and they do an orderly shutdown. Or they should Which is exactly what panic does.
- troupo 2y ago--- start quote --- Panic is a built-in function that stops the ordinary flow of control and begins panicking... The process continues up the stack until all functions in the current goroutine have returned, at which point the program crashes. --- end quote --- This is far from orderly. For example, what happens to other goroutines? I also like how golang docs literally describe using panics as poor man's exceptions: --- start quote --- For a real-world example of panic and recover, see the json package from the Go standard library. It encodes an interface with a set of recursive functions. If an error occurs when traversing the value, panic is called to unwind the stack to the top-level function call, which recovers from the panic and returns an appropriate error value ... The convention in the Go libraries is that even when a package uses panic internally, its external API still presents explicit error return values. --- end quote ---
- usrbinbash 2y ago> I also like how golang docs literally describe using panics as poor man's exceptions: And I like how https://github.com/golang/go/issues/26799 https://github.com/golang/go/issues/26799 describes that use of panic in the original version of this bog entry from 2010: quote: However, this is a poor example and a misuse of panic and recover as exceptions. See https://golang.org/doc/effective_go.html#panic https://golang.org/doc/effective_go.html#panic The example will be invalid soon with the release of go 1.11 as well. The panic/recover was removed from the unmarshal code. See master's end quote. The blog entry was later changed because this was fixed. It now refers to marshaling which, sadly, sill uses this mechanism. The fact that this is still in the json package is a pain point, yes. Does it validate the use of panic as a form of flow control? No. Here is what "Effective Go" has to say about the topic: https://go.dev/doc/effective_go#panic https://go.dev/doc/effective_go#panic quote: But what if the error is unrecoverable? Sometimes the program simply cannot continue. For this purpose, there is a built-in function panic that in effect creates a run-time error that will stop the program (but see the next section). end quote.
- williamdclt 2y ago`recover` is still useful for unrecoverable errors, eg to capture telemetry then propagate the panic again
- DanielHB 2y agoIn my old project we used recover to give a HTTP 500 response, trigger a log/alert and restart our HTTP router and its middlewares in case of panic in some function. Restarting like that was faster and more stable than crashing the whole thing and restarting the whole server. But it is a bit dangerous if you don't properly clean up your memory (luckily most APIs are stateless besides a database connection)
- bilekas 2y agoMaybe I’m mistaken but isn’t that a hand crafted attack vector to taking down your services?
- knome 2y agohopefully, anything triggering a panic should be an exceptional case that the caller cannot purposefully put the server into. restarting the app without a full process reload shouldn't be any more of an attack vector for denial of service than restarting the entire app.
- tgv 2y agoThere may be libraries that call panic. E.g., the templating library does that. In that case, I want something in the logs, not a termination of the service.
- gizzlon 2y agoIIRC, the Must.. functions are typically used at program start, and in cases where you would like the program to stop. At least that's the way I've used it. For example to read and parse expected templates from disk. If they aren't there, there really is no reason to continue, it's just very very confusing. https://pkg.go.dev/html/template@go1.24.0#Must https://pkg.go.dev/html/template@go1.24.0#Must
- arccy 2y agothe template and regexp packages have Must variants that panic. They're intended to crash the program on init because of programmer error.
- usrbinbash 2y ago> I thought that's why it's called "panic". And you are exactly right. The problem is: People are so used to the "exceptions" paradigm from other languages, when they see "panic-recover" many immediately think "That's the same thing!!" It isn't, because the only VALID usecase for panics is exactly what you describe: unrecoverable error conditions where terminating the program is the best course of action. `panic/recover` used like exceptions is an antipattern, and one of the worst code smells in a Go codebase.
- lblume 2y agoExactly, same as panic! in Rust. There is a reason Rust was reluctant to add std::panic::catch_unwind at first. The docs thus explicitly mention that (1) it is not a typical exception mechanism and (2) that it might not even catch panics if unwinding is disabled (common for embedded and restricted development).
- kmeisthax 2y agoAlso (3) there are panics that can't be safely caught, such as stack overflow.
- jub0bs 2y agoThere are even panics that are intended as irrecoverable: https://cs.opensource.google/go/x/sync/+/refs/tags/v0.12.0:singleflight/singleflight.go;l=167 https://cs.opensource.google/go/x/sync/+/refs/tags/v0.12.0:s...
- the_gipsy 2y agoYou do need to use it, not to handle errors but to avoid it taking down the whole process (and probably sending some logs / alert / monitoring). Which doesn't apply everywhere, but at least in web dev it does: if a request / task panics, you want to abort just that, not the whole server including any other requests / tasks running. Sadly, you need every goroutine to have its own recovery handler. This works well for your general request / task entrypoints, as there should only be one for each kind, but you need to watch out for any third-party libs spawning goroutines without recovery. They will take down your whole server.
- amanj41 2y agoI think a good usecase for recover is in gRPC services for example. One wouldn't want to kill the entire service if some path gets hit leading to a panic while handling one request.
- seanw444 2y agoSome are of the opinion that that should be handled a layer up, such as a container restart, because the program could be left in a broken state which can only be fixed by resetting the entire state.
- oefrha 2y agoGiven that you can’t recover from panics on other goroutines, and Go makes it extremely easy to spawn over goroutines, often times it’s not even an opinion, you have to handle it a layer up. There’s no catchall for panics.
- __turbobrew__ 2y agoThis is a major pain in the ass. I was trying to solve the problem of how do you emit a metric when a golang service panics, the issue is that there is no way to recover panics from all goroutines so the only way to do that reliably is to write a wrapper around the ‘go’ statement which recovers panics and reports them to the metrics system. You then have to change every single ‘go’ call in all of your code to use this wrapper. What I really want is either a way to recover panics from any goroutine, or be able to install a hook in the runtime which is executed when an unhandled panics occurs. You can kind of fudge this by having the orchestration layer look at the exit code of the golang process and see if it was exit code 2 which is usually a panic, but I noticed that sometimes the panic stack trace doesn’t make it to the processes log files, most likely due to some weird buffering in stdout/stderr which causes you to lose the trace forever.
- closeparen 2y agoCorporate gRPC services are written with "if err != nil" for every operation at every layer between the API handler and the db/dependencies, with table-driven tests mocking each one for those sweet sweet coverage points. I would love a community norm that errors which fail the request can just be panics. Unfortunately that's not Go as she is written.
- stouset 2y agoSometimes you don’t even want to recover, just do something like log it remotely so it can be seen and debugged. Sometimes it can’t reasonably be handled until some natural boundary. A server that handles multiple connections at once can produce an unrecoverable error handling one of those connections, but it still should gracefully close the connection with the appropriate response. And then not kill the server process itself.