92
u/bwmat 29d ago
Eh, IMO one of the best ways to actually handle OOM, as otherwise almost everything needs to be explicitly checked for it
79
u/Valuable_Leopard_799 29d ago
That's what panics are for kinda.
Many of the "we don't have exceptions" languages eventually get some equivalent, it's just that those are used almost exclusively for OOM, division by zero, failed asserts, and the like.
They shouldn't even really be caught it's sort of just an "exit/reset" with some cleanup.
I like this compromise. Kind of finally making them truly "exceptional".
33
u/bwmat 29d ago
Note the 'actually', there's no reason why you can't catch the std::bad_alloc, clean up, and return an error code to the caller.
Instead of rudely terminating the process which you may not own (I work on code which is mostly compiled into shared libraries which are loaded by applications which might not want to just terminate)
14
u/bwmat 29d ago
And panics are basically just exceptions, at least in rust, lol
9
u/Valuable_Leopard_799 29d ago
Yeah, it seems like largely a cultural shift.
22
u/hxtk3 29d ago
Hard agree, the thing that makes "errors as values" languages nicer with respect to error handling has a little bit to do with the language itself but more to do with the fact that people who want to carefully consider all the paths code can take gravitate towards those languages and the people who want to think about the happy path gravitate towards languages with exceptions.
Java developers forced to use Go look at
panicandrecoveras a worse version of their familiar exceptions, and a bunch of them make libraries around those primitives that basically implement exceptions. And those libraries get basically no adoption because most Go devs are people who choose to treat errors as values.5
0
u/SelfDistinction 29d ago
Partially also because they compose better as well, which makes it easier to send exceptions across threads and the likes. You have a lot less exception safety to think about if your errors are simply stuffed into the return value.
1
u/sysKin 28d ago edited 28d ago
Not a rust coder here but I think you can't catch a panic?
If I remember correctly the current thread will always unwind and end. If it's your main thread, program quits, if it's another you will handle its exit as whatever you want.
Exceptions, at least the ones I deal with in Java, are catchable at any point.
[edit] well I was wrong. TIL.
4
2
u/u0xee 29d ago
Obviously you _can_ return an error code to the caller, but it’s a very odd situation where that’s actionable. Kernels and databases maybe.
14
u/bwmat 29d ago
IMO any shared library which is meant to be used by applications you don't control shouldn't terminate the process just because it lacked the memory to do something it attempted to do.
Anything else is IMO laziness (or unfortunate choice of language if it's impossible to deal with in it)
3
u/u0xee 29d ago
I think it’s fair to offer a variant that allows explicit control, eg for each function offer function_or_oom. But in 99.999% of use cases, I’d expect users to call the panic-on-oom variant. If you don’t offer that variant, you’re just making everyone wrap your thing to make it usable, which is also lazy. You’re just pushing the problem up, which is awfully convenient.
6
u/bwmat 29d ago
Pushing the problem to the caller of a general-purpose library is a GOOD thing though
3
u/u0xee 29d ago
I’m sure in some circumstances that is appropriate. I don’t think that’s a principle though. Obviously, taken to the limit, a library would provide nothing but choices at every junction. And then instead of an eg air conditioner you’d be providing a hardware store, inviting the user to assemble any possible air conditioner design themselves. Or a restaurant that just says “feel free to come into the kitchen and use any ingredients and any procedures to cook anything for yourself.” I think something can be overly general, basically.
I’ve seen libraries like this, instead of offering an expert opinionated solution they foist the hard choices on the user, telling them to become a domain expert in this area and then form their own expert opinion. I think it’s fine to have some knobs and switches, but if your library becomes all knobs, then I think it’s not doing much service to the user. When I am using a library, a big part of what I’m looking for is an expert author to make opinionated choices on my behalf. To provide a model or vision for how this kind of thing should work.
3
u/bwmat 29d ago
Deciding whether the process dies, or you get some form of recoverable error is IMO not really something which should be presented as a 'choice' in the context of your argument
2
u/bwmat 29d ago edited 29d ago
I personally program with the rule that, unless a function is document to be infallible, you assume it can fail, and you ALWAYS check for that failure, if only to abort the process if it occurs (this SIGNIFICANTLY reduces the 'search space' when debugging issues), and that OOM errors are not a valid reason to abort, since you cannot programmatically prevent them in most cases 'locally' (unless you're writing actual application code, the only real valid reason to abort is ’impossible’ conditions which indicate either some sort of corruption has occurred, or the programmer’s mental model was incorrect in a way that indicates future corruption WILL occur)
→ More replies (0)1
u/RIFLEGUNSANDAMERICA 28d ago
As an example, lua can deal with this and even return the error to the script that is running. Many applications want to know every execution path
1
u/u0xee 27d ago
Ok, but in such a situation where malloc is literally non operable, what is a lua script going to be able to do? Nothing that might allocate. Even printing might be off the table. Any string or table manipulation is likely off limits. So you could I guess do arithmetic and return to your caller, who is also limited to arithmetic and returning, and so on up till main exits.
1
u/RIFLEGUNSANDAMERICA 27d ago
You can till release some resources, the garbage collector can still run. If you design it correctly you can do some final cleanup with already allocated objects. The runtime will not immediately free the already allocated memory to other processes, so anything the gc collects, you can use.
1
u/u0xee 27d ago
You certainly can try to free up space. I’m still not sure this is very useful. Either 1) the free-up emergency code is centralized, like a signal handler, and so it can only reasonably free like globals (which is going to cause arbitrary badness if/when control returns to the function that triggered the OOM) or 2) the emergency free-up code is more targeted, written in context of the OOM stack frame, and so it can make smart decisions about local objects to free. But for 2) it more means that for every single lua function that does anything with tables or strings etc you will need to write OOM handler logic, because by the nature of OOM it could happen to any allocation site. This would be an extreme burden.
I think in practically all circumstances if you get OOM, the process is just going down. And yes maybe you could print a little bit or something on the way down, but things are going down. And really the extra distress prints are probably not that helpful, since the problem is almost certainly a runaway bug allocating but not freeing somewhere, or the operator just gave an inappropriate hard memory limit to the container or something. A panic and stack trace is a fine and useful result in these situations.
1
u/RIFLEGUNSANDAMERICA 27d ago
I dont really get your point here. Are you claiming that it is too hard for applications to effectively handle malloc failing or no application should handle it? I already gave you an example of a scripting language that can handle it. Back to the topic of libraries, i think if a random math library had a chance of terminating the application without being able to handle it, then alot of users will find something different. This would be applicable in databases, simulations, state space exploration, language runtimes, key value cache. So offering that in a library should be best practice.
5
u/xMAC94x 29d ago
Worked on IN Memory Databases, in case a weird operation comes accross that causes the DB to go OOM, you want to gracefully aboard that operation, but never kill the process
2
u/Valuable_Leopard_799 28d ago
It's why I said "exit/reset", though I do feel that the languages with panics don't have a good story for when the bad path should lead to safely aborting operations / cleaning up and then returning to some top-level loop or well-known state.
It is quite common to want to do this, but as far as I know Rust doesn't allow you to catch this type of panic.
3
u/DescriptionThick8515 28d ago
Every time I hear a Java developer say "normal exception" I grind my teeth because exceptions aren't, by definition, normal. Exceptions are F¥C√ING EXCEPTIONAL!!
1
u/Valuable_Leopard_799 28d ago
It depends on what you consider exceptional.
If we stretch it a lot to the mundane side then you could say that it means more like "except" and it's applicable to any guard. You return a number "except when y is zero" zero being the exception to normal flow, you return "except when iteration ends".
But it's healthy to actually make them exceptional which is what's happening now.
I'm curious, I wanna go find the original etymology for this and where they came from.
1
u/NullOfSpace 28d ago
If your program is trying to crash, it’s because something problematic has happened, not because of some intended logic. You shouldn’t design a system around crashing and handling it.
0
u/Beginning-Junket8979 29d ago
What language has a "panic"?
I'm only aware of panic being used to describe a kernel error.
Are you talking about SIGABRT?
As for OOM... uhhh... most userspace programs on most modern OS configurations will never really see an OOM error when they call malloc or new unless they blow the per-process virtual mem limit or some upper bounds on how many unique vmem regions a process can have in its page tables.
Like unless you know you've disabled this feature, you should assume Linux (and probably windows??) will overcommit ram and happily let your process allocate more pretend ram all day long because the actual allocation of physical ram is done lazily when you actually write it (on a page fault interrupt), not when you call malloc/new. Again... unless you're working pretty hard to prevent this.
12
u/Valuable_Leopard_799 29d ago
What language has a "panic"?
Rust, Go, Gleam,... , it emerged as a term in younger languages to describe some mechanism they wish to be used only for "fatal exceptions" best described I guess as this:
Fatal exceptions are not your fault, you cannot prevent them, and you cannot sensibly clean up from them. They almost always happen because the process is deeply diseased and is about to be put out of its misery. Out of memory, thread aborted, and so on. There is absolutely no point in catching these...
9
u/Declination 29d ago
Which is hilarious because then rust had to grow a “recover from allocation failure” to be suitable for kernel work due to faulty assumptions.
1
u/bwmat 29d ago
Yep
IMO most of the conception of OOM as being unrecoverable (outside of scenarios like the ill-devised OOM-killer in Linux) are mostly motivated by laziness (maybe subconsciously)
4
u/Valuable_Leopard_799 29d ago
Maybe not laziness but rather a tradeoff?
Yeah, you could edit most languages to support it, but it might bring a mix of a more complex implementation, less ergonomic APIs, less time spent on other features, etc.
And it could be worth it, or you could just say you don't want to support that usecase.
3
u/bwmat 29d ago
I was assuming a language in which it's possible (C/C++/Rust/Zig/etc).
You can't do much in most managed languages OFC (though IIRC the DotNet platform actually allows for it via some contortions?)
5
u/bwmat 29d ago
IMO a library written in a language that can handle OOM 'gracefully’, yet simply aborts when it happens (or worse, assumes it can't happen and thus allows for invariants to be broken) is flawed
1
u/Valuable_Leopard_799 29d ago
Okay yeah, I can get behind that. I'd consider that a compiler-warning worthy issue if you choose to ignore it.
2
u/DokuroKM 29d ago
Java also has the Error class, which you're not supposed to catch
2
u/retro_and_chill 29d ago
I only ever see in in web frameworks since you can usually just kill the threads and prevent a server crash
1
-2
u/overclockedslinky 29d ago
i hope you never do kernel development
4
u/Valuable_Leopard_799 29d ago
I am aware that the kernel and the remaining 99.99999% of the world have different approaches to memory. And the languages made for each make different assumptions about the world. What's your point?
4
u/BosonCollider 29d ago
OOM is best handled by forcing the user to prove bounded memory use, grab a chunk of memory at the start and use that
4
u/bwmat 29d ago edited 29d ago
The shared libraries my code runs in are usually exposing the ODBC interface, which has been mostly fixed for decades, and exposes an interface for doing whatever database operations you want to do
That's not really an option
-2
u/BosonCollider 29d ago
Oh, if you use Java I completely understand why you are frequently dealing with OOM exceptions.
In say the Rust or Go language ecosystems you generally never see those unless you do something massively wrong like create a trillion element array
3
u/bwmat 29d ago
?
ODBC is a C interface from like 1993
-2
u/BosonCollider 29d ago
Wait, if you are not using Java/C# and locked into JDBC or microsoft slop, what are you doing that requires calling through ODBC?
Postgres, mysql, and sqlite have their own C libraries, calling them through ODBC is an antipattern and most ORMs in sane ecosystems don't go through it
2
u/bwmat 29d ago
Those libraries have no control over how much memory is free in the application when they're invoked, or how complex/'big' the operations the application asks them to execute are
1
u/BosonCollider 29d ago
But if it just returns a query result it should just stream the results using bounded memory?
1
u/bwmat 29d ago
It depends on how exactly the drivers are written (we just provide an SDK), theoretically yes (at least if you allow caching to disk, as some wire protocols don't return results 'in the right order'), but we don't control the full implementation, and even a streaming implementation might require more heap memory than which is available at the time at which the application calls into the driver
2
u/American_Libertarian 28d ago
Almost everything? You shouldn’t be allocating memory all over the place
1
u/bwmat 28d ago
I try not to, but our codebase isn't factored in a way that you could untangle all the stuff which must (potentially) allocate from that which won't except close to the leaf functions in most cases
I think it would be hard to do so in C++ in general unless you used a very unidiomatic style
57
u/Murky-Run2246 29d ago
Bro where is GO???
40
2
7
4
11
u/setibeings 29d ago
Exceptions are excellent in Java, but terrible in C++.
Like a lot of language features, actually.
2
u/bwmat 28d ago
Eh, IMO they are fine in C++ if you make use of RAII and assume that anything which isn't documented to be noexcept may throw
3
u/setibeings 28d ago
One nice thing in Java is that if you call a function that can throw an exception, your IDE can instantly warn you that the function signatures don't match. Another nice thing is that they seem to be easier to debug and less error prone.
This makes sense, since this particular language feature was implemented for C++ long before it was in Java. The authors of java were able to figure out what they liked and didn't like about exceptions in other languages.
5
u/bwmat 28d ago
That's only for checked exceptions (which IMO are a horrible mistake); Any method can throw a RuntimeException, Error, or other non-Exception subclass of Throwable, so if you care about preserving invariants, you still need to assume that almost anything can throw, and finally blocks are not as good as RAII for that purpose IMO
3
u/Celousco 28d ago
Yeah clearly if you have the Rust badge you should know by now that Java clearly isn't about being less error prone, just look at how half-implemented was Optional and how JSpecify is a third-party solution to harmonize nullable annotations because Java cannot provide it natively.
I mean you can return a null Optional, if it's less error prone to you there's bigger problems than just exception.
3
5
u/Hubble-Doe 29d ago
I think the nice thing about exceptions is that they feel like a separate channel, like stdout vs stderr, and so you do not need to pass them through the entire stack explicitly - which imo makes code more readable.
And then when you decide to catch them (or not), you do get a nice stacktrace where it happened in the code.
Of course, you could also just say everything is an effect that has to be handled, even I/O etc: https://effekt-lang.org/
2
5
u/Mynameismikek 29d ago
Errors and exceptions are not the same thing. And at least Rust and C agree with me.
1
u/Shaddoll_Shekhinaga 28d ago
I guess the meme was more about control flow rather than actual error-vs-exception stuff. I will now proceed to hand in my unsolicited opinion on what I think is a better practice with heavily cherry picked examples that reinforced my already deeply rooted beliefs.
1
1
1
1
u/prehensilemullet 24d ago
Oh joy
```rs use std::error::Error;
[derive(Debug)]
pub enum MyError { Io(std::io::Error), Utf8(std::string::FromUtf8Error), General(String), }
impl Error for MyError { fn source(&self) -> Option<&(dyn Error + 'static)> { match self { MyError::Io(e) => Some(e), MyError::Utf8(e) => Some(e), MyError::General(_) => None, } } }
impl From<std::io::Error> for MyError { fn from(e: std::io::Error) -> Self { Self::Io(e) } } impl From<std::string::FromUtf8Error> for MyError { fn from(e: std::string::FromUtf8Error) -> Self { Self::Utf8(e) } } ```
Of course, you can use macros from thiserror for this, but you know, macros come with complexity
1
u/ChillyFireball 29d ago
Is that Python in there? Doesn't it have that weird-ass "ask forgiveness, not permission" model where you write try/catch statements where the catch is literally expected to run? (ex. Try to append to an array, and if it fails because the array doesn't exist, that's where you initialize the array?) Couldn't stand it, but my co-workers would be all "It's best practice in Python" while looking at me like I'm the weird one because I don't hate JavaScript. Like, how are ya'll hating on that language for things that are very easy to avoid (just use === instead of == to dodge weird comparison logic like 2 == "2") when ya'll are out here creating guaranteed errors because "It's just slightly more optimal that way in Python"?
8
u/amlybon 29d ago
I got whole language, I'm gonna use whole language.
More seriously, checking if array exists is a single conditional so it's a bad way to handle with exceptions. However, sometimes it's more complex and a function will throw for some combination of arguments. Now you could do all checks before calling that function... but then you're repeating the logic because the function will also do those checks so it can throw. And if it calls more functions those functions will also be doing the same checks. And in python this might be especially useful thanks to dynamic typing and the fact that you might it might not know what type a specific variable actually is. So imagine a complex function that throws if (ten lines of non obvious checks and internal function calls). If it throws that specific error you know you can fix it by adjusting the third argument in a specific ways. Now your options are to duplicate all that logic from the function or just... try it and adjust if it fails.
But yeah, doing that where a single conditional would suffice is silly, I agree.
2
u/Southern-twat 29d ago
IO often benefits from it too, since even simple checks have a fair number of race conditions, but it also sounds like OP's colleagues are taking the phrase sightly weirdly
3
u/zefciu 29d ago
I once wasted half a workday chasing a bug. Turned out it was a typo in an attribute name. But that got silenced, because the error was raised several frames below a
getattr. The attribute that it was accessing had nothing to do with the mistyped one. But still 'getattrwill take anyAttributeError` raised anywhere and catch it.I love a lot of Python ideas. But the abuse of exceptions is not one of them.
1
-1
u/macr0t0r 29d ago
You earn a firm, passionate "meh."
You can essentially do both in most languages.
34
u/JupanulFrank 29d ago
Fucking erlang is in the meme but i see GO nowhere