views:

294

answers:

4

Hi all,

I've created a simple Outlook 2007 add-in using C# which loops through a selection of Messages and examines their attachments.

I'm running this add-in on a set of ~25,000 selected Messages. Immediately, however, I notice the memory usage of Outlook (seen via perfmon) shooting up. After running the add-in in debug mode, line-by-line, it is apparent that memory is assigned to Outlook upon the first instance of accessing a Message's Attachments collection. This memory is never returned to the system; Outlook continues to eat memory until it hits ~1GB (after about 12,000 Messages), whereupon I receive an "out of memory or system resources" error. Any ideas?

Here's part of the code:

        for(int i = 1; i <= objSelectedItems.Count; i++)
        {
            Object objMsg = objSelectedItems[i];

            //Only process if Item is a Message
            if (objMsg is Outlook.MailItem)
            {
                Outlook.MailItem Msg = objMsg as Outlook.MailItem;

                //The culprit: this allocates memory to Outlook which I can't get back
                Outlook.Attachments objAttachments = Msg.Attachments;

                //Perform some actual work here//

                //Clean up Outlook objects; does not appear to give memory back to system
                Msg.Close(Microsoft.Office.Interop.Outlook.OlInspectorClose.olDiscard);
                Marshal.ReleaseComObject(objAttachments);
                Marshal.ReleaseComObject(Msg);
            }

            Marshal.ReleaseComObject(objMsg);
            GC.Collect();
            GC.WaitForPendingFinalizers();
        }
A: 

If nothing else I'd check the Msg object for attachments first before calling this line:

 Outlook.Attachments objAttachments = Msg.Attachments;

otherwise you're allocating for each and every message, regardless of presence of attachment... so if there are only 5,000 messages with attachments, that should be done only 5,000 times instead of all ~25,000 times

curtisk
Such a conditional, like 'if(Msg.Attachments.Count > 0) { }', also causes the same amount of memory to be allocated.
NickL
+1  A: 

Are you using a foreach loop for processing the attachments (that part is left out in your code snippet)?

According to a blog post foreach causes a memory leak whereas for does not:

OOM.NET: Part 2 - Outlook Item Leaks

Apparently there is also a Hotfix available fixing various issues regarding memory leaks.

UPDATE

Have you tried freeing each single attachment contained in the attachments collection?

for (int i = 1; i <= oAttachs.Count; i++)
{
    Outlook.Attachment oAttach = oAttachs[i];

    // Do nothing with attachment
    Marshal.ReleaseCOMObject(oAttach);
    oAttach = null;
}
0xA3
I was using a foreach this morning, but I stumbled upon that OOM.NET article earlier today and changed my code to the for loop you see above to no effect. I'm just starting looking into the hotfix you posted...
NickL
So the for vs. foreach was not the cause. Anyway, OOM seems to be a a little tricky to work with. Did you try running your code as you posted it (i.e. without the "//Perform some actual work here" part that you left out)? Does that still have a memory leak? Otherwise the problem woule be in that specific part of the code (Anyway I would try commenting parts of your code until the memory leak disappears; the debugger may not show you the correct place in the code).
0xA3
Yeah, the code I posted is literally what I'm running. I have the "actual work" code implemented but commented out for the time being. The culprit is definitely that line where I first access Msg.Attachments (be it an assignment to a new object like in my sample or a simple conditional to check, say, Msg.Attachments.Count). Without that line, I have no problems (but, of course, no way to access Attachments either).
NickL
As of this morning, it doesn't appear the hotfix helped any. I also tried releasing each attachment individually - unfortunately that, too, doesn't do the trick. Thanks for your assistance so far on this; I'm still investigating the issue so I'll post back again if I ever discover a solution.
NickL
A: 

Did you try to check that Marshal.ReleaseComObject() always return 0 maybe you have additional references somewhere?

Moreover did you find any Dispose items. Then you should call Dispose()

Foxfire
I just investigated this suggestion - my calls to Marshal.ReleaseComObject() always return as <= 0, which I presume is OK.
NickL
A: 

I seem to have solved the issue. Since objSelectedItems was brought in via Applicaiton.ActiveExplorer().Selection, I did the following:

  1. Copy each Object in objSelectedItems into a locally declared List.
  2. Call Marshal.ReleaseComObject(objSelectedItems).
  3. Begin my loop as posted in the Question above - though modified to use the local Object List instead of the Outlook Selection.

This apparently means that, for some reason, the Selection had to be released before the individual objects within it could be released.

NickL