Re: bad alloc
On Aug 31, 2:18 pm, Adam Skutt <ask...@gmail.com> wrote:
Programs that do more than one function are at a net loss with "die on
OOM" approach, and the loss is bigger the more the functions there are
(and the more important they are. Imagine an image processing program.
So you apply a transformation, and that OOMs. You die, your user loses
his latest changes that worked. But if you go back the stack, clean
all those resources transformation needed and say "sorry, OOM", he
could have saved (heck, you could have done it for the user, given
that we hit OOM). And... Dig this: trying to do the same at the spot
you hit OOM is a __mighty__ bad idea. Why? Because memory, and other
resources, are likely already scarce, and an attempt to do anything
might fail do to that.
Trying to do it at any point is a mighty bad idea. Rolling back the
stack and deallocating memory _does not ensure_ future allocations
will succeed. Once you reach OOM, you're not assured the user will be
able to save; you're not assured you can do anything at all. Trying
may very well be pointless.
It may be, but you are less helpless than what you are making it out
to be.
Say that you want to save. If your file stream is already there, and
given that saving is logically a "const" operation, there's little
reason for things to go wrong (it's possible, but not likely). Or say
that you want to log the error. Logging is something that must a no-
throw operation, therefore, logging facilities are already there and
ready.
I tried, a long time ago, to eat all my memory and then proceed to
disk I/O. This works on e.g. Unix and windows. Why wouldn't it?
It's considerably smarter, and absurdly easier, to save off the file
before attempting the operation in the first place. This way, your
code can die in a clean fashion, and the user has a recovery file with
the changes up to the failed operation. Trying to create the recovery
file under or after an OOM condition is very difficult. However,
trying to create the recovery file before the OOM condition is
trivial.
You can't be serious with this. I would really like to see a codebase
that saves state to disk prior to any allocation (any failure
condition, really).
Actually, what could be attempted is saving after any change. But that
won't work well for many-a-editor either. Best you can reasonably do
is to save recovery from time to time.
In all of the counter-examples everyone's provided so far, the correct
way to ensure reliability is not to attempt to avoid crashing on OOM.
The correct way is to write your code so that crashing on OOM becomes
irrelevant. This has the added bonus that your code will still be
reliable even if your process is never told of the OOM condition. As
I mentioned before, in some languages you're not promised to be told
about allocation failure, and other have mentioned that on some
operating systems your process may simply die instead of being told
about the failure.
Or imagine an HTTP server. One request OOMs, you die. You terminate
and restart, and you cut off all other concurrent request processing
not nice, nor necessary. And so on.
That may happen anyway, as I explained to Yan. The situation is much
harder to handle when there is concurrent processing occurring, not
easier.
Straightforward C++ on most implementations will deallocate memory as
it goes, so when the application runs out of memory, there won't be
anything to free up: retrying the operation will cause the code to
fail in the same place. Making more memory available requires
rewriting the code to avoid unnecessarily holding on to resources tha=
t
it no longer needs.
That is true, but only if peak memory memory use is actually used to
hold program state (heap fragmentation plays it's part, too). My
contention is that this the case much less often that you make it out
to be.
No, I don't believe most applications unnecessarily cache data and
would benefit from attempting to free those caches under OOM
conditions. Even if I did, the code has to be written to make that
possible, which is very difficult.
It's not unnecessary caching, it's transient peaks in memory usage
during some work. You often don't know how much memory a given system
has, nor you don't know what e.g. other processes are doing wrt memory
at a time you need more memory.
Even when there's memory to free up, writing an exception handler tha=
t
actually safely runs under an out-of-memory condition is impressively
difficult.
I disagree with that, too.
Then I shudder to think about what you actually find difficult. Have
you ever tried to do this? Your commentary strongly suggests not only
have you not, but you don't have any experience in building reliable
systems anyway.
But I have. I have been intentionally droving code up the wall with
memory usage and looked at what happens. If you have your resources
prepared up front, it's not hard doing something meaningful in that
handler (depends also what one considers reasonable).
First off, when you actually hit the top-
level exception handler, chances are, you will have freed some memory.
Which doesn't mean you can make use of it. When you get
std::bad_alloc, you must assume there's 0 bytes available. No new, or
malloc, etc., anywhere.
Second, OOM-handling facilities are already made not to allocate
anything. E.g. bad_alloc will not try to do it in any implementation
I've seen.
It's not the behavior of std::bad_alloc that's problematic.
I've also seen OOM exception objects pre-allocated
statically in non-C++ environments, too (what else?).
Figuring out what you need to handle an OOM condition is not a trivial
task. Quick, I want to write out a file during OOM (using iostreams)
and have it not fail due to lack of memory. Tell me everything I must
do to ensure this.
Quick? Why? Because the way to write a critical piece of code is off
the top of one's head? That's not serious.
There is difficulty, I agree with that, but it's actually trivial: keep=
in mind
that, once you hit that OOM handler (most likely, some top-level
exception handler not necessarily tied to OOM), you have all you might
need prepared upfront.
Which is exceptionally hard. Let's say I'm going to attempt your file
writing idea. I have to have the stream itself already open and
ready. I have to make sure the internal streambuf has enough memory
to buffer my I/O (or is unbuffered). I have to make sure my data
structures are constructed in such a fashion so that no memory
allocation occurs when I access the data, and that the data is
accessible when the handler runs. This means no copying of anything,
which is not impossible to ensure but difficult.
Meh. You are trying to construct a case of trying to do a lot in case
of a resource shortage in order to prove that __nothing__ can be done
in case of resource shortage. I find this dishonest.
Realistically, here's what I'd do for save case:
try
{
throw zone, lotsa work
}
catch(const whatever& e)
{
inform_operator(e, ...); // nothrow zone
try { save(); } // throw zone
catch(const whatever& e)
{
inform_operator(e, ...); // nothrow zone
}
}
Then, I would specifically test inform_operator under load and try to
make it reasonably resilient to it. But whatever happens, I would not
allow exception to escape out of it. That's all there is to it
conceptually, and practically, there's always room for later
improvement, but __without__ changing conceptual model.
You gloss over the difficultly, especially when you don't know what
code will allocate memory, nor when it will do it. Memory allocation
can and does occur in places you don't expect, so ensuring memory
allocation doesn't happen means being intimately familiar with the
implementation of every type involved in your OOM handler.
Personally, I have better things to do then familiarize my self with
the inner guts of my C++ runtime implementation.
Yeah, I agree that one cannot sensibly "handle" bad_alloc. It can
sensibly __report__ it though.
Reporting is handling and you're kidding yourself by trying to
distinguish them.
I disagree. For me, there's actually no such thing as "error
handling". There's error reporting and there's __program state
handling__ (in face of errors). This is IMO a very important
distinction.
Even reporting may not be possible. Reporting
requires I/O, I/O requires memory and resources.
Most exceptions
occur because: 1) you're a bad programmer 2) you're out of some
resource (that you probably ran out of doing I/O anyway).
Hmmm... We are most likely in disagreement what are exceptions used
for.
Goran.