Golang panic crash prevention
Asked Answered
S

3

16

In Golang a panic without a recover will crash the process, so I end up putting the following code snippet at the beginning of every function:

defer func() {
    if err := recover(); err != nil {
        fmt.Println(err)
    }
}()

just in order to prevent my program from crashing. Now I'm wondering, is it really the way to go? Because I think it looks a little bit strange to put the same code everywhere.

It seems to me, the Java way, bubbling the exceptions up to the calling function, until the main function is a better way to control the exceptions/panics. I understand it's by Go's design, but what is the advantage of immediately crashing the process just like what Go does?

Skill answered 18/8, 2014 at 5:1 Comment(10)
You shouldn't think of panics as the Go equivalent to Java's exceptions. They're used much more rarely. In Java, they're used to indicate any kind of error condition. In Go, on the other hand, the idiom for indicating an error is to return an error as the last return value (for example, see os.Open). Thus, panics are reserved for cases which should crash the program like nil pointer dereferences.Undertenant
(there are some exceptions to this rule - panics can be useful in rare circumstances as a programming construct - but in general they're only used for unrecoverable errors)Undertenant
Yes, but in order to write a robust server programmer, especially an extensible plugin or interceptor system, you really should not let a plugin or interceptor easily crash your main server, am I right?Skill
Correct, there may be a need for this behaviour (Go's net/http Server uses recover to catch panicking goroutines), but at the same time you didn't ask about that. Panic/defer/recover is the exception - use them only when needed and never any more.Muscat
Thanks @elithrar, I understand I should be careful when using panic/recover. However, if I use other people's library, that will be out of my control. Yes, to be safest, I can do defer/recover at every of my function, that is what I do now. I'm just wondering, what is the advantage to crash a process so easily, compared to Java's bubble up model?Skill
You should think of a Go recover as the equivalent of, in Java, catching RuntimeException or NullPointerException or something like that. You wouldn't ever see that code in a Java application.Undertenant
@synful, yes, agreed, however, in Java RuntimeException or NullPointerException do not crash the process. I don't see any advantage crashing the process over bubbling up to callers until main.Skill
Or maybe panic is misused, it actually means System.exit(1) in Java? If this is the case, should the failure of network connection call System.exit(1) in Java?Skill
I don't have an easy way to run java code at the moment, but I fail to believe it works as describe. If I have a main() that spawns a thread and that thread throws an exception, am I to believe that main will somehow catch the exception? More likely, the exception will just be lost and nobody will know. The program will simply be in an undesirable state. We like desirable states.Borghese
In Java, it's not possible for a spawn thread to throw a non-runtime exception, or the code will not compile, because Runnable.run does not declare to throw anything. However, it's possible to throw a runtime exception, throwing a runtime exception will not crash the main thread.Skill
U
13

You should only recover from a panic if you know exactly why. A Go program will panic under essentially two circumstances:

  • A program logic error (such as a nil pointer dereference or out-of-bounds array or slice access)
  • An intentional panic (called using panic(...)) from either your code or code that your code calls

In the first case, a crash is appropriate because it means that your program has entered a bad state and shouldn't keep executing. In the second case, you should only recover from the panic if you expect it. The best way to explain this is simply to say that it's extremely rare, and you'll know that case if you see it. I'm almost positive that whatever code you're writing, you don't need to recover from panics.

Undertenant answered 18/8, 2014 at 5:5 Comment(12)
What I'm doing is I'm writing a server waiting for incoming connections. When a incoming connection connects, the server starts a go routine to handle that incoming connection. I don't want the server to crash if any go routine panics. I don't even care what happened in the go routine. If it failed, it failed. In this case, it seems I have to make sure I have to recover any possible panic in the go routine's code. Am I right, or am I missing anything?Skill
If your program panics, it indicates a programming error. Any normal error that you would want to ignore (like a network timeout or badly formatted request) will come in the form of an error value returned from a function, not a panic.Undertenant
This is another case: github.com/elgs/gorest/blob/master/data_interceptor.go this interceptor interface is intended to be overridden by future development. I think panics is too overkilling that anyone can easily crash the whole process, unless recover is put everywhere.Skill
@ElgsQianChen panic() in go crashes your program in the same way as a NULL-pointer dereference in C would do. In neither case you would really want your program to continue executing.Desulphurize
I was in the same situation. On my server, I receive JSON data from a WebSocket. I'm then decoding it and making type assertions on it, assuming the data is correct. For a normal use, that can't fail, but what if the data has been altered ? Isn't recovering panics the best thing to do to be sure that a hacker (for example) can't crash my server this way ?Ceceliacecil
@sebcap26 No, not panicking is the best way to prevent your server from crashing. Sounds like you're doing exceedingly complicated and fragile stuff stuff with your input. Do less fragile and less complicated stuff with your input and you should find it easier to work with, faster, safer, and crash-free.Borghese
@sebcap26, there's a way to do type assertions without causing a panic. If you do val, ok := interfaceValue.(Type), the second return value from the type assertion (that is, the value placed in ok) will be a boolean indicating whether the assertion succeeded. Using this, if the assertion failed, it will simply make ok false instead of panicking.Undertenant
@synful, I think we are talking about two different topics. Yes, we can try to be careful enough to avoid panicking. However, the question is about should panic without recover immediately crash the process, and why? Maybe it's difficult to implement when some variables are on the heap, I guess. It should be easy to clear the stack, when it panics(go)/throws exception(java), it just needs to simply clear the call stack. However, the data on the heap involves GC. It might not be easy, or might be expensive to do so. So maybe the Go designers decided to just crash the process, I guess.Skill
Honestly, it's not an issue of avoiding panicking. If you write a program that panics, and you didn't do it intentionally (as in, you didn't call panic(...) yourself), your program has a bug. No program should ever panic unless it explicitly wants to. You can think of it like dividing by 0 or accessing memory you don't own in C. It's not an error like a network timeout - it's a bug. You wouldn't want to recover from it, because it means your program is doing something wrong. It means you need to fix your program.Undertenant
I would say that it depends. If you write a critical system, and some subsystem panics, you could recover from it and try to start it again after 10 seconds. This would be useful in cases where continuing to run a buggy system in an unknown state would be better than crashing. E.g. in a pacemaker ;-)Inspissate
I think this answer is misleading. Go is not C - the language detects and prevents access to garbage memory, throwing a panic instead. The program is not "in a bad state and shouldn't continue executing" - unless maybe you forgot to put critical cleanup code in a defer call. Using panic+recover is a language feature which is meant to be used in many legitimate cases. eli.thegreenplace.net/2018/…Raynata
logic error is not as you described. logic error caused by wrong logic and causes an wrong result. for example, consider pow(a, b) method which results 2^0=0!! Your second bullet needs to use error handling because we expected it. Panic is designed for situations that you can not handle it and routine must be terminated. recover mechanism designed to control how it will be done.Whitelaw
A
3

Generally, even with exceptions, you catch them at a "FaultBarrier". It's usually the place where all new threads are spawned. The point is to catch and log unexpected failures.

In Go, you use return values for all expected failures. The framework in which you work will generally have a fault barrier to catch a session (ie: usually an http transaction) and log the problem. The only other place I see recover happening is things like non-idempotent Close function. If you have a situation where you can't tell if something is already closed but know it must be closed, then you could end up doing a recover so that a second close panic will be ignored, rather than failing what you are doing all the way up to the FaultBarrier.

Amaliaamalie answered 28/8, 2016 at 14:56 Comment(0)
W
1

I think panic is not same as exception. You can handle exception and running of routine will be continued. Whereas panic causes to termination current routine and you can not skip it.
Exception emits by OS generally and causes to run related routine. Instead panic emits by programmer manually and causes to exit goroutine.
You can define multiple exception for every piece of code within a function. Panic recovery mechanism works for whole function.
Exception designed to be handled whereas Panic designed to termination and panic recovering mechanism seems to be just a trick to control termination.

So exception handling is comparable with error handling.
But how you can take advantages of it in Golang?

I will describe my use case to answer your question.

There is two type of blocking error, Panic and Fatal. You can not recover from fatal error.
Sometimes you need to kill the process. But sometimes you need to restart it.
I used recover() mechanism to recovering from panic error in order to shutting down current goroutine and restarting main functionality.
So I must be careful about error type. Some situations need to emit fatal error such as missing necessary config file. And sometimes it is reasonable to restart the app. Think about all situations that you like to restart the app after crash. Such as:

  • overloaded service (which causes to DoS)
  • missing DBMS
  • unexpected error origin from other used go packages
  • crashing imagick process for example
  • and so on

So,
recover() is very beneficial in my case. It gives a chance to shutdown the process clearly before exiting.

Side Note: You can develop an bootstrapper app which will runs main app as detached process. It must re-run main app, if that process exited abnormally.
Use logging in order to debug while keeping your app run.

Whitelaw answered 23/8, 2021 at 12:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.