9 ms·
There are a few surprises of Go, but one thing that surprised me the most is the fact that an unrecovered panic in any goroutine will crash the whole program. T
by beltsazar 3y ago
There are a few surprises of Go, but one thing that surprised me the most is the fact that an unrecovered panic in any goroutine will crash the whole program. To make things worse, you can only recover a panic in a goroutine that you spawn. So, you can't do anything to prevent a panic in a goroutine spawned by a third-party library crashing your whole program. The best you can do is to set up a supervisor to restart your program after crash.
- galangalalgol 3y agoRust made the same call. Panic isn't supposed to be "I'm not sure what to do now, maybe my caller will know?". That is an error not a panic amd you return it. A panic is for "I just realized I or someone else did something really bad and the invariants of everything are now in question, hit the breakers, lets try this again". If your dependency library, like so many, panicked rather than take the time and effort to check the conditions that would cause an out of bounds access, then they are bad and they should feel bad, fork and merge the fix and don't say anything, they will feel the shame.
- quickthrower2 3y agoRunning off forks is no fun though.
- hackerbrother 3y agoSo the question is: should there exist a method that means “bring down the whole ship”. (Or, should that method be something more severe than “panic”, and panic should be redefined with a more limited scope.)
- quickthrower2 3y agoI agree. I like C# where you can expect libraries to play nice. In theory it is possible for C# library to crash your app but it is definitely not on the trodden path. But I do prefer return values over exceptions! I have not used Go and I think the err != nil boilerplate might drive me a little insane, but Haskells option types seem a nice fit.
- beltsazar 3y ago> Rust made the same call. Completely different. In Rust when you spawn a thread [1] or an async task [2], you'll get a handle from which you can check if a panic happens inside the thread/task. Even if you ignore the handle, a panic inside a thread/task won't crash the entire program. --- [1] https://doc.rust-lang.org/std/thread/fn.spawn.html https://doc.rust-lang.org/std/thread/fn.spawn.html [2] https://docs.rs/tokio/latest/tokio/task/fn.spawn.html https://docs.rs/tokio/latest/tokio/task/fn.spawn.html
- dinosaurdynasty 3y agoYeah I've been hit by this, I've setup a panic handler to purposefully kill the entire process because otherwise long-running threads die and don't restart (and it was easier to do it that way).
- zoky 3y ago> Panic isn't supposed to be "I'm not sure what to do now, maybe my caller will know?". So panic is supposed to be “I’m not sure what to do now, I don’t care if my caller knows”? No process running outside of kernel space ever has any business panicking. Throw an error and move on with your life (or don’t, that’s fine too) but don’t punish your caller by forcing it to crash because you failed to foresee X, Y, or Z circumstances.
- xyzzy_plugh 3y agoIf it's reasonable for the caller to continue then return an error. If the state of the world is irreconcilable, panic is the right call.
- zoky 3y agoAnd how is the called method supposed to be able to determine whether the state of the world is irreconcilable? For that matter, how is the called method supposed to determine what the state of the world even is? And why on Earth would we trust it to do so? Is encapsulation not even a thing anymore? I really just don’t understand.
- bogeholm 3y agoA classic example is: - Accessing array out of bounds: panic - File not found: error
- nishs 3y agoThere are two "kinds" of panics: those initiated by the Go runtime, and those initiated by the program author, though technically there isn't a distinction. When you access an array out of bounds, the Go runtime initiates the panic. On the other hand, for a condition that the program should hold but doesn't, the program author should initiate the panic. For example, in the exhaustive switch below, reaching the default case means that either you have somehow incorrectly allowed the construction of an invalid token value earlier in your program, or you have missed to account for a valid token value in this function. func (t token) symbol() byte { switch t { case add: return '+' case sub: return '-' case mul: return '*' case quo: return '/' default: panic("unknown token") } } Errors are different. For example, url.Parse returns an error if its input string is an invalidly formatted URL. package url func Parse(rawURL string) (*URL, error)
- masklinn 3y ago> Rust made the same call. It did not though? - threads implicitly catch_panic at their boundary, and convert that to a `Result` which you can receive when you join on the thread: https://doc.rust-lang.org/std/thread/type.Result.html https://doc.rust-lang.org/std/thread/type.Result.html - in theory an async runtime could shut down on panic, but tokio at least does not, much like `std::thread::spawn` it'll catch_panic and return a JoinError to however is await-ing the joinhandle: https://docs.rs/tokio/latest/tokio/task/struct.JoinError.html https://docs.rs/tokio/latest/tokio/task/struct.JoinError.htm... If you drop the handles, the error just is not signaled: - sync: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=b20cc7c1af5ca42485e83820b1c1e56f https://play.rust-lang.org/?version=stable&mode=debug&editio... - async (tokio): https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=25f424fd6ce6f463d28508633741626c https://play.rust-lang.org/?version=stable&mode=debug&editio...
- galangalalgol 3y agoYeah I think I remember that being a controversial decision when it was made for threads? One of the valid reasons for a panic is that you've encountered something that makes you think your adress space is in a bad state. Threads share an address space, so the reasoning was that if they panic, so should the process, but that isn't what happened.
- DangitBobby 3y agoThen why does recover() even exist? Programs seem to continue just fine after a recover.
- usrbinbash 3y ago> To make things worse, How is a panic crashing a program a bad thing? Panics are not Exceptions. They usually are the result of something happening that should crash the program.
- beltsazar 3y agoYes, "usually", but not always. That's why Go has a mechanism to handle a panic with the `recover` function. The issue is more on the inability of handling a panic in a "foreign" goroutine.
- eloisant 3y agoBut you should have a supervisor anyway, if you want your service to be always up. Unless you're working on a CLI tool that is short lived anyway (and represents a lot of projects using Go).
- goldinfra 3y agoSounds like you may be confused thinking that a panic in Go is like throw in other languages? Third-party libraries should virtually never panic. Go has panic, and it's useful for a few things, but does not use them as exceptions are used in other languages. This is a bit like worrying that your programming language won't prevent a third-party library from sending a SIGKILL to your program or that it doesn't prevent third-party libraries from delete your entire file system. No general purpose language protects against malicious or stupid code.
- jamescostian 3y agoI've seen apps break in prod where third party libraries had array accesses on indices that didn't exist, or dereferenced nil pointers, in goroutines they spawned. And those aren't the only ways to cause a panic without ever calling the panic function. Third party libraries have bugs sometimes, and other times our usage of them can create bugs. Nothing like a SIGKILL or anything malicious. That's just the way things are and will be. And breaking down like this in dev can be fantastic! But when a once in a blue moon issue crops up in prod and all other connections are dropped, the fact that a simple defer+recover couldn't have been preemptively placed on all goroutines is a huge nightmare. I'd even take a "you can add defer+recover for any goroutines created further in this callstack" - it's rare that libraries spawn goroutines, I'll already know which ones to trigger that behavior on
- beltsazar 3y ago> But when a once in a blue moon issue crops up in prod and all other connections are dropped, the fact that a simple defer+recover couldn't have been preemptively placed on their goroutines is a huge nightmare Yeah, this is one of the weird things, to say it mildly, that Go does that is hardly mentioned anywhere. Most people don't know about it until they themselves first encounter the nightmare bug similar to what you said.
- goldinfra 3y agoWrapping every third-party library in defensive code doesn't make a lot of sense since this isn't the most dangerous kind of bug. To make a one example of a million: a third-party database library that corrupts your database is infinitely more dangerous and that there's no form of wrapping that will save you. Bad code is bad code. Bugs are bugs. This issue is not a major source of problems in Go code.
- Mawr 3y agoA panic means your program has entered an invalid state. Continuing to run an arbitrarily broken program would be reckless, so crashing is the only option. Even if you could somehow prevent your program from crashing internally, it could be killed at any time by the environment it's running in, up to the cleaning lady tripping on the power cable. That's why it doesn't make sense to try to prevent crashes. Instead, programs should be capable of resumption irrespective of when they were killed. Resumption should be facilitated by supervisors layered on top of the program, as needed.
- Too 3y agoPanics are definitely one of the bigger design mistake of go. They should have either removed panics entirely and made error as return values the only way to do signal errors or they should have made defer+recover more mainstream like exceptions. Current state of affairs is that third party libraries panic where they shouldn’t, programs don’t defer where they should, recovers cause deadlock due to non deferred mutex releases, that assumed panics are panics and not exceptions. On top of that we have to live with the verbose error handling that still isn’t water tight.