If you’re handling an out of memory exception, you probably shouldn’t allocate memory


With the assistance of Application Verifier, specifically, low resource simulation (also known as fault injection), a tester found a stack overflow condition. As we learned earlier, the important thing to look at when studying a stack overflow is the repeating section.

Contoso!Error::ThrowError+0x39
Contoso!Str::Set+0x35
Contoso!Win32::OpenModuleName+0x54
Contoso!StackTrace::StackEntry::FindModuleInfo+0x1b
Contoso!StackTrace::CreateTrace+0x2c
Contoso!StackTrace::StackTrace+0x4f
Contoso!Error::Error+0x1f

When this stack trace was shown to the development team, they instantly recognized the cause of the problem. And you also have enough information to figure it out, too.

Hint: Of the most likely reasons that a method named Str::Set would throw an error, which of them match the scenario?

Since we are simulating low resources, the error being thrown is most likely an out of memory error.

Reading the stack dump, the constructor for the Error object builds a stack trace object, and the stack trace object tries to allocate memory for a string in order to do its job. But that memory allocation fails, because we are out of memory, so an Error object is thrown, which builds a stack trace, which encounters an out of memory error, and so on.

Obviously, the mistake was allocating memory as part of the process of reporting an out of memory condition. You need to be careful to avoid generating the very error that caused your error handler to be called.

Related topics:

Comments (14)
  1. Medinoc says:

    What bothers me is that it’s not the same memory here: The error says we’re out of stack space, but the allocation in (probably) from the heap…

    [Trying to report the heap failure results in recursive death leading to stack overflow. And since nobody tries to catch stack overflows, that is what ultimately gets reported as the “cause” of the crash. But the root cause is the heap exhaustion. -Raymond]
  2. I tend to consider memory an "infinite resource with finite use", in the same way as threads, windows and other similar resources: there certainly is a limit on how much I can use, but if one of my apps is using a lot of it, there is a design problem somewhere. In modern machines, with enough RAM to fit the entire 2 Gb user address space into physical memory, out of memory errors use to happen only if the code is leaking memory or otherwise wasting memory (except for memory intensive applications, of course!).

  3. feroze says:

    This is a very good point. That is why the CLR preallocates the OutOfMemoryException object, and can throw it whenever it reaches an OOM situation, even those in which it couldnt allocate a OOMException object, even if it wanted to.

  4. Me says:

    In the old days of C, on out-of-memory error you usually printf-ed a message "out of memory" and exited the application with an error code.

    Nowadays it’s much more complicated — you throw an exception which from the user’s point of view does exactly the same thing — shows the message "out of memory" and exits the application unless of course it encounters an out-of-memory condition.

    Such is the way of progress.

  5. Ben Voigt [C++ MVP] says:

    On Windows (and most other modern OSes, ignoring kernel-mode drivers), "out of memory" almost always means "out of virtual address space for this process".

    Does AppVerifier cause the related but much rarer these days "exhausted virtual memory"?  ("out of RAM" doesn’t seem to be considered a problem, even though it hurts performance terribly, is frequently a sign of a bug, and users typically want to cancel the operation rather than continuing)

    The distinction between exhausting virtual address space and virtual memory (i.e. RAM+swapfile+can’t grow swapfile) is important for a couple reasons.  One, virtual memory is a shared resource, the process that suffers the failure is not necessarily the process that consumed an inordinate amount, and the severity of the failure can vary as threads of other processes are scheduled.  Virtual address space, on the other hand, is per-process which means you know who used it up as well as having some ability to set some aside for the error handler without risking another application snagging it as soon as it becomes available.

    I’m referring to VirtualAlloc(reserve,2MB) at process startup and (suspend other threads) VirtualFree when the error is trapped, making memory allocation suddenly work long enough for the handler to do its thing.

    To do that for virtual memory exhaustion, you’d have to actually commit the holdout memory, using a much scarcer resource, and then use it in-place.  Trying to hand that block over to your normal allocator creates a race condition with all other processes.

  6. Jonathan says:

    The same thing just happened to me. A logger failed to initialize, and behold, the failure has Log("bla bla failed"). A stack overflow ensued.

    I need to fix this logger. And the failure to begin with.

  7. TDL says:

    Ah.  This one goes on the same list as not trying to log database connectivity exceptions to the database. Tried that — it doesn’t work! :)

  8. Me says:

    @Ben Voigt:

    There was a bug in Program X long time ago (in the era of Windows 98) — it leaked memory so hard and fast (say between 50 and 100 MB/sec) that if you had an unlimited (system managed) swap file it always brought down the whole system in the most horrible way.

    I worked around that bug by setting the upper limit for the swap file which made only the program crash, instead of taking the system down with it. Since then I always use fixed swap file size.

    As for virtual address space exhaustion that is way too easy to accomplish if you need a lot of memory, especially now with address space randomization when DLLs are loaded. When you allocate and free memory, virtual address space becomes heavily fragmented and the largest allocation size keeps shrinking until you can’t allocate enough for the next block. Ah, joys of Windows programming…

    [Not just Windows programming. Programming in general. -Raymond]
  9. Anonymous Coward says:

    Amen to that Raymond. If I had a Euro for every time my Linux box killed of something essential because of an out of memory condition, like the X server, the window manager or the shell, I would buy myself a pretty nice laptop and still have plenty change.

  10. SuperKoko says:

    "

    Amen to that Raymond. If I had a Euro for every time my Linux box killed of something essential because of an out of memory condition, like the X server, the window manager or the shell, I would buy myself a pretty nice laptop and still have plenty change.

    "

    Just disable overcommitting:

    echo 2 > /proc/sys/vm/overcommit_memory

    echo 100 > /proc/sys/vm/overcommit_ratio

    Processes may still die if they try to allocate memory but don’t gracefully handle failed allocations (e.g. stupid GNOME programs using g_new).

  11. Greg says:

    What I don’t understand is that in C++ this shouldn’t ever happen. If construction of the object you are trying to throw also throws an exception, std::terminate gets called which kills the process. Thus you would have a maximum of 2 failed memory allocations before crashing, not the recursive death spiral.

    It certainly looks like C++, but what language is it?

    [If you look carefully you’ll see that the recursive death is in preparing to throw. I’ll leave reconstructing the code as an exercise. -Raymond]
  12. I remember developing an application back in the early 90’s that was fairly memory intensive. We would preallocate a buffer so that when an out of memory exception occurred, we could free this buffer, tell the user to save all his stuff and quit. This at least allowed the user to not lose work.

  13. Jolyon Smith says:

    @feroze:  A neat idea that the CLR borrowed from the VCL.  ;)

  14. Ben Voigt [C++ MVP] says:

    @The person calling himself "Me":

    I’m fully aware of the causes of real-world address exhaustion, as well as the advantages of having a runaway program experience an out of memory error without spending hours swapping to/from disk first.

    My point was simply that there are two reasons for allocation failure (not counting hardware faults) and wondering which one AppVerifier is simulating.

Comments are closed.

Skip to main content