2006-12-02

OutOfMemory, or maybe not?

I've been writing an app for the compact framework for some months now. It's quite a complicated app that includes an object persistence framework, a task oriented application layer and a loosely coupled GUI which is generated through factories (the app only has 1 form, but lots of user controls + factories).

The app has been experiencing apparently random OutOfMemoryExceptions, no matter how hard I have tried I have found it impossible to reproduce one of these errors. I have spent quite some time really optimising the memory useage of my OPF so that it works on the bare minimum of memory yet still operates quickly enough (and I'm very pleased with its performance too). However, the OOM exceptions persisted!

I wrote a logging tool which records the last X actions the user performs, when an unexpected exception occurs this log is written to disk along with a stack trace of the exception. I noticed that the top of the stack trace always read...

at Microsoft.AGL.Common.MISC.HandleAr()
at System.Windows.Forms.Control._InitInstance()
at System.Windows.Forms.Control..ctor()
So it seems that my app failed each time I tried to create a user control. Considering my GUI gets updated by disposing of the current control and then replacing it with a control created by a factory this happens quite a lot, but nowhere more frequently than when I am importing data (updated every 500ms). So now I know a good candidate for finding the bug but no way to actually reproduce it in my app. Strangely enough it wasn't what the user was doing in my application that mattered but what they were doing in another one.

It would seem that if the PPC application is doing something that takes a few seconds (on a 350Mhz CPU that isn't rare) the user decides to pop off to another part of Windows and check the battery level, check the memory usage or just generally "muck about". Finally I had the last piece of the jigsaw! I ran my data import and then started to select/deselect files in the Program Manager. OutOfMemoryException!

The strange thing was that whenever this exception occured I would have at least 5MB of RAM free. It would seem that if a CF WinForms app tries to call Microsoft.AGL.Common.MISC.HandleAr() at the same time some other app is changing its GUI the call will fail with an OutOfMemoryException. So I decided to put the control create in a loop like this
int triesLeft = 20;
while (true)
{
try
{
return {Something confidential that creates the control}
}
catch (OutOfMemoryException outOfMemoryException)
{
triesLeft--;
if (triesLeft == 0)
throw outOfMemoryException;
Thread.Sleep(500);
}
}
The hope was that if I tried up to 20 times, half a second apart, the chances of another app updating GUI at the same time would be very low. Unfortunately even if I closed all other apps during this loop the creation of the control would still fail. It seems that once it fails there is no hope, all is lost, it just packs up and refuses to work at all!

Instead of updating the GUI straight away I started a timer "GuiTimer" with an interval of 100ms. The Tick event of this timer first disables the timer and then creates the control needed. Maybe the Timer messages sent be WinCE are all done from a single thread or something, I don't know because I haven't looked into it. This doesn't fix my problem, but it does make it occur less often.

Despite my attempts to create a small app to reproduce this problem I have not been successful, yet I can reproduce it easily in my main application.

The above attempt however made no difference, .NET will perform a garbage collection before throwing an OutOfMemoryException so it was really quite pointless. A much better solution to my problem has been to create my user controls only once and then to keep hold of them, reusing an existing instance seems to almost completely eradicate my bug.

1 comment:

Anonymous said...

I am having this exact same issue. I am reusing my instances through and I still have the issue. The only way to get rid of the bug is to load all of my user controls but that kind of defeats the purpose as I wanted to use less memory.