8

I'm currently working on an exception-based error reporting system for Windows MSVC++ (9.0) apps (i.e. exception structures & types / inheritance, call stack, error reporting & logging and so on).

My question now is: how to correctly report & log an out-of-memory error?

When this error occurs, e.g. as an bad_alloc thrown by the new op, there may be many "features" unavailable, mostly concerning further memory allocation. Normally, I'd pass the exception to the application if it has been thrown in a lib, and then using message boxes and error log files to report and log it. Another way (mostly for services) is to use the Windows Event Log.
The main problem I have is to assemble an error message. To provide some error information, I'd like to define a static error message (may be a string literal, better an entry in a message file, then using FormatMessage) and include some run-time info such as a call stack.
The functions / methods necessary for this use either

  • STL (std::string, std::stringstream, std::ofstream)
  • CRT (swprintf_s, fwrite)
  • or Win32 API (StackWalk64, MessageBox, FormatMessage, ReportEvent, WriteFile)

Besides being documented on the MSDN, all of them more (Win32) or less (STL) closed source in Windows, so I don't really know how they behave under low memory problems.

Just to prove there might be problems, I wrote a trivial small app provoking a bad_alloc:

int main()
{
    InitErrorReporter();  

    try
    {
        for(int i = 0; i < 0xFFFFFFFF; i++)
        {
            for(int j = 0; j < 0xFFFFFFFF; j++)
            {
                char* p = new char;
            }
        }
    }catch(bad_alloc& e_b)
    {
        ReportError(e_b);
    }

    DeinitErrorReporter();

    return 0;
}

Ran two instances w/o debugger attached (in Release config, VS 2008), but "nothing happened", i.e. no error codes from the ReportEvent or WriteFile I used internally in the error reporting. Then, launched one instance with and one w/o debugger and let them try to report their errors one after the other by using a breakpoint on the ReportError line. That worked fine for the instance with the debugger attached (correctly reported & logged the error, even using LocalAlloc w/o problems)! But taskman showed a strange behaviour, where there's a lot of memory freed before the app exits, I suppose when the exception is thrown.


Please consider there may be more than one process [edit] and more than one thread [/edit] consuming much memory, so freeing pre-allocated heap space is not a safe solution to avoid a low memory environment for the process which wants to report the error.

Thank you in advance!

dyp
  • 38,334
  • 13
  • 112
  • 177
  • 2
    Have you considered having a block of memory reserved in advance that will become the source of placement allocations whenever the system gets into an out-of-memory situation? I've only ever used this method for the sake of exiting gracefully from the application, but operating systems (OpenSolaris, Linux) do something similar to give the application enough time to free or swap out low-priority allocations and recover gracefully. – Sniggerfardimungus Aug 20 '10 at 17:48
  • Currently, I use some stack space (member variables declared when calling InitErrorReporter) to provide buffers to CRT / WinSDK functions. But I don't know what they do internally - see the Alex Farber's anser. – dyp Aug 20 '10 at 18:34
  • http://stackoverflow.com/questions/1308052/policy-with-catching-stdbad-alloc talk about something similar – Chubsdad Aug 23 '10 at 03:23
  • possible duplicate of [What's the graceful way of handling out of memory situations in C/C++?](http://stackoverflow.com/questions/3596990/whats-the-graceful-way-of-handling-out-of-memory-situations-in-c-c) – Billy ONeal Sep 04 '10 at 23:42
  • Sorry about the dupe message -- meant to put it on another question. – Billy ONeal Sep 04 '10 at 23:42

4 Answers4

3

"Freeing pre-allocated heap space...". This was exactly that I thought reading your question. But I think you can try it. Every process has its own virtual memory space. With another processes consuming a lot of memory, this still may work if the whole computer is working.

Alex F
  • 42,307
  • 41
  • 144
  • 212
  • k, but supposed there where two threads in one application where both ran different libraries' functions. One process -> one virtual memory space. So when one throws a bad_alloc then frees pre-allocated space, the other one could allocate it? The problem is when I use OS / SDK functions, I don't know if they internally rely on head space, so using pre-allocated space to operate on was no solution I thought. – dyp Aug 20 '10 at 18:31
  • have I to suspend all other threads before I can use this technique? – dyp Aug 20 '10 at 18:44
  • Another threads may immediately use pre-allocated memory which is just released... Every activity should be stopped to be sure that error report succeeds. – Alex F Aug 20 '10 at 18:57
2
  • pre-allocate the buffer(s) you need
  • link statically and use _beginthreadex instead of CreateThread (otherwise, CRT functions may fail) -- OR -- implement the string concat / i2a yourself
  • Use MessageBox (MB_SYSTEMMODAL | MB_OK) MSDN mentions this for reporting OOM conditions (and some MS blogger described this behavior as intended: the message box will not allocate memory.)

Logging is harder, at the very least, the log file needs to be open already.

Probably best with FILE_FLAG_NO_BUFFERING and FILE_FLAG_WRITE_THROUGH, to avoid any buffering attempts. The first one requires that writes and your memory buffers are sector aligned (i.e. you need to query GetDiskFreeSpace, align your buffer by that, and write only to "multiple of sector size" file offsets, and in blocks that are multiples of sector size. I am not sure if this is necessary, or helps, but a system-wide OOM where every allocation fails is hard to simulate.

peterchen
  • 40,917
  • 20
  • 104
  • 186
  • 1
    thank you very much. I did some few tests (yet I don't know how signifacnt they are) and the MessageBox | MB_SYSTEMMODAL always showed up, even after catching several bad_allocs. For this, I used some code based on the one in my own answer, but decided to build it for x64 / Release. The bad_allocs I received with x86 were mostly out-of-virtual-address-space errors since I have 4 GB of RAM plus (now) 2 GB max page file size. With the x64 version, the system really hangs severly, even the cursor sometimes. But still, MB works and that trick with the page I mentioned, too! – dyp Aug 23 '10 at 20:05
1

Please consider there may be more than one process consuming much memory, so freeing pre-allocated heap space is not a safe solution to avoid a low memory environment for the process which wants to report the error.

Under Windows (and other modern operating systems), each process has its own address space (aka memory) separate from every other running process. And all of that is separate from the literal RAM in the machine. The operating system has virtualized the process address space away from the physical RAM.

This is how Windows is able to push memory used by processes into the page file on the hard disk without those processes having any knowledge of what happened.

This is also how a single process can allocate more memory than the machine has physical RAM and yet still run. For instance, a program running on a machine with 512MB of RAM could still allocate 1GB of memory. Windows would just couldn't keep all of it in the RAM at the same time and some of it would be in the page file. But the program wouldn't know.

So consequently, if one process allocates memory, it does not cause another process to have less memory to work with. Each process is separate.

Each process only needs to worry about itself. And so the idea of freeing a pre-allocated chunk of memory is actually very viable.

TheUndeadFish
  • 8,058
  • 1
  • 23
  • 17
  • Huh? This is not correct. While every process has its own VA space, there are a limited number of physical frames and swap space to satisfy a page-in request. If the total commit charge goes above this number, every process's allocation requests fail. If you get into this situation, freeing a chunk of memory means any process in the system is up to taking it (i.e. any process will be able to again raise the commit charge). – Ana Betts Aug 20 '10 at 23:22
  • @Paul: You have a point. No matter what the OS does there will always be a limit eventually. Though, outside of situations where the memory mechanisms of the OS have been maxed out, I wonder if it isn't reasonable for programs act as if they will always have their full virtual address space to work with. But I guess that can be situation-specific, depending on how likely the end-user machine will max out it resources and how useful it would actually be to log in response to such a situation. – TheUndeadFish Aug 21 '10 at 00:21
  • Yeah, there is a limit. It's the size of your hard drive. That's a lot of memory. On the vast majority of machines, the RAM is less than 50% allocated, and there's so much HDD space available, the reality is that your process only has to worry about itself. – Puppy Aug 23 '10 at 12:38
  • I think the main limit is the virtual address space in 32 bit applications. See http://msdn.microsoft.com/en-us/library/aa366778(VS.85).aspx but that's not the main problem. When your only problem is that your address space is full (how to determine that?) you could just release a chunk of memory and continue work. – dyp Aug 23 '10 at 12:46
  • @DeadMG It isn't the size of your HD, it's the preconfigured size of the swap file, which is usually around 2-4GB – Ana Betts Aug 23 '10 at 17:31
  • @Paul Betts: Windows can increase that size whenever it likes. – Puppy Aug 23 '10 at 17:36
  • @DeadMG It can dynamically increase the size up to a point, but there is a limit – Ana Betts Aug 23 '10 at 17:47
  • @Paul Betts: Yes, the limit being the size of the harddrive, assuming that you're on NTFS. Really, most processes use only a tiny, tiny fraction of their 4GB, and the reality is that there's ample free physical memory to go around, even when you start capping your 2GB. – Puppy Aug 23 '10 at 21:42
  • 2
    @DeadMG http://blogs.technet.com/b/markrussinovich/archive/2008/11/17/3155406.aspx - "The maximum is either three times the size of RAM or 4GB, whichever is larger" – Ana Betts Aug 23 '10 at 23:08
  • Yes, so earlier in the article, he allocated 29GB. Where did that all go? – Puppy Aug 24 '10 at 00:40
  • I think DeadMG, you're talking about a "physical" limit (a limit of OS capabilities) whereas Paul Betts cites the limit of the OS' auto size mode. In that article Paul Betts referrs to, the second last paragraph deals with this auto mode (= max size choosed by the OS -> 3*RAM or 4 GB) whereas the very last paragraph deals with the limit of OS capabilities (-> 32-bit Windows [...] 16TB [, ...] 64-bit [...] 16TB [...]. For all versions, [...] up to 16 paging files [...].) – dyp Aug 24 '10 at 07:48
0

You can't use CRT or MessageBox functions to handle OOM since they might need memory, as you describe. The only truly safe thing you can do is alloc a chunk of memory at startup you can write information into and open a handle to a file or a pipe, then WriteFile to it when you OOM out.

Ana Betts
  • 73,868
  • 16
  • 141
  • 209
  • So you mean, rather than pre-allocate and then free a chunk, you would instead pre-allocate an area to be used specifically to be used for handling the data recorded in the OOM scenario (a sort of scratch buffer for assembling the strings or whatever is desired). Assuming WriteFile doesn't ever have a reason to fail in an OOM scenario, then I'd have to agree that this sounds like the best guaranteed way to record the information. – TheUndeadFish Aug 21 '10 at 00:30
  • Yes, this is what the OS does if it has to guarantee that something works in out-of-memory conditions (like being able to raise an SEH exception, ntdll always keeps one preallocated in its back pocket) – Ana Betts Aug 22 '10 at 22:47
  • Do you know how the creation of the different kinds of Win32 objects behaves in OOM conditions? I.e. Kernel objects such as files (-> file handles) and GUI objects such as MessageBoxes? As far as I know, at least Kernel objects are not part of your virtual memory / adress space and therefore the OS actually allocates memory for them. – dyp Aug 22 '10 at 23:04
  • 1
    @DyP: You're wrong. The OS does indeed allocate inside your address space. However, the way the two are separated, it'll probably be fine. That's why on a 32bit machine you get 2GB of limit, not 4GB- because the OS keeps the other 2GB for whatever it needs. However, if you fail an allocation, it's because your half is full - not necessarily the whole space. There's no guarantee that the OS 2GB is or isn't in any state, including full or not. – Puppy Aug 23 '10 at 12:40
  • @DyP Kernel objects also require memory, and they are also subject to system-wide OOM - if an allocation fails, it will end up propagating back using GetLastError – Ana Betts Aug 23 '10 at 17:31
  • @Paul: Sure. I didn't doubt that ^^ but since I didn't found anything about it on MSDN, i thought it was better to ask. Maybe there was already some reserved memory part from the OS where it could store Kernel objects (physically). (But it does not seem so) – dyp Aug 23 '10 at 17:53
  • -1. Actually, MSDN documents that a message boix wit MB_SYSTEMMODAL can be used to report out-of-memory conditions. Some Microsoftie blog documented that it was expressly implemented to do that in a default setting (MB_OK | MB_ICONHAND | MB_SYSTEMMODAL as far as I recall). Will try to dig it up. – peterchen Aug 23 '10 at 19:22