3 ms·
I unfortunately quite hate the direction of not being able to write conforming programs that handle out-of-memory in a way that's distinct from terminating. Tha
by anarazel 7y ago
I unfortunately quite hate the direction of not being able to write conforming programs that handle out-of-memory in a way that's distinct from terminating. That's a great default for a large majority of systems, but e.g. for a database server it sucks. When the system is configured appropriately (which is not the default, and has its own sets of problems, in particular with fork()), postgres e.g. handles the vast majority of out-of-memory cases gracefully, by just failing the query that encountered the exhaustion.
With this proposal, that's effectively impossible to do, without either being non-conforming ("with the additional restriction that any installed new_handler must terminate"), or not using any standard library code (which is proposed to not throw any exceptions: "For each standard function for which allocation is the only reportable error, make the function noex-cept").
It's practically impossible to use only explicitly checked allocations everywhere. One can do it for allocations that are more likely to fail (e.g. because they're large), but not for every small thing. In postgres, it's my experience that a very significant fraction of real-world allocation failures are on small allocations, not huge ones.
Now postgres is currently C (but that's imo more historical baggage than anything, and has pretty significant costs), but this would effectively prevent us from taking advantage of a lot of C++ if we ever decided to move.
FWIW, postgres OOM handling basically works by doing allocations via it's 'memory contexts', which check allocation failure (obviously only really possible if the OS signals them, requiring e.g. to explicitly change the overcommit settings on linux). Allocation failures are handled by switching to a memory context that sigsetjmp's to a higher level. That higher level then releases resources by bulk-releasing memory using the memory context mechanism, and handles other resources (locks, other resources) via the related resource owner mechanism. But that mechanism is - as far as I know - impossible to do in realistic confirming C++, because one cannot call all the relevant destructors.
- wahern 7y agoI'm not a C++ programmer but I noticed this conflict. C++ is adding features like coroutines that perform hidden allocations, while at the same time shifting away from exceptions. You can't have it both ways. You can have hidden allocations, you just need an exception mechanism. It sounds like Postgres does this with setjmp/longjmp. That's typical. I do that with my JSON C library--https://github.com/wahern/json/blob/master/src/json.h#L72 https://github.com/wahern/json/blob/master/src/json.h#L72 And setjmp/longjmp is how Lua implements its exceptions. Lua is interesting in that it's a dynamically-typed scripting language that permits recovery from memory allocation failures in a rigorous manner. It doesn't add much if any complexity to the Lua implementation--if anything the discipline arguably helps keep things simpler. But it does require the Lua VM authors to always be mindful about maintaining consistency of the VM state. In particular it means they have to make sure all dynamically allocated memory is properly anchored in the object graph. This type of discipline isn't something you can refactor for after the fact, but it's not as difficult as it seems if you start out this way (modulo the lack of compiler enforcement). The Lua C API is also carefully designed--most routines are implemented and documented to not do any hidden allocations. And unlike any other scripting language with a C API I've used or examined, Lua provides a plethora of convenient abstractions for keeping foreign data pointers anchored in the object graph, as well as the mechanisms and semantics that permit both C and Lua code to catch and reliably recovery from exceptions, including allocation exceptions. By contrast, while Go conceptually could provide OOM recovery--it has an exception mechanism almost identical to Lua's, and likewise uses non-copying GC--but much of the implementation makes the assumption that allocation never fails. As a practical matter correct, reliable, and consistent recovery from OOM isn't possible for Go. As I said, it's not the type of thing you can add after the fact. So instead you see a recapitulation of decades of myths[1] and contrivances[2] focused on "managing" memory. I only pick on Go because massive concurrency, and in particular massively concurrent network sessions, is it's niche. Even at "web scale" where you have the same Go process running on dozens or hundreds of other machines, this neglect to tackle allocation failure has resulted in often times exceptionally poor quality of service for Go apps. Without proper allocation failure recovery, "managing" memory becomes a black art rather than a science, with predictable results in terms of consistency and stability. It's a shame to see C++ gleefully going down this road, seemingly having finally drunk the Kool-Aid and adopted the myth that handling allocation failure to too difficult and too error prone; that it's not your problem and something else higher up the stack will solve the problem for you. :( [1] E.g. big allocations are more likely to fail [2] E.g. reserving large blocks to be released according to some imaginative heuristic intended to predict and forestall allocation failure
- anarazel 7y ago> It's a shame to see C++ gleefully going down this road, seemingly having finally drunk the Kool-Aid and adopted the myth that handling allocation failure to too difficult and too error prone; that it's not your problem and something else higher up the stack will solve the problem for you. :( I think the reasoning behind this part of the C++ proposal at hand is a bit more complicated than that. It's imo worth reading section 4.3 specifically about the memory allocation issue, and the motivation for the whole exception overhaul in in section 2 (although that's a bit longer). The reasoning isn't itself that it's too difficult or such, it's that the overhead required by something like the current exception handling is high, and that it's quite desirable to makr a lot more of the standard library as not throwing exceptions - and the only cause a lot of them throw, is OOM. http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0709r0.pdf http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p070...