The Intentional Memory Leak

So - there's a memory leak in Outlook's implementation of MAPI. That shouldn't be too much of a shock. Just about any sizable application is bound to have a leak or two. What's interesting about this leak is that it is intentional.

A little history: We first shipped Simple MAPI back in MS Mail, circa 1992. Later on, when Extended MAPI was being developed for Windows 95, we chose to reimplement Simple MAPI as a wrapper around Extended MAPI. This reduced bug counts, simplified testing, etc. Everyone was happy. (Or so I've been told - I was still in school at the time!)

As we neared RTM however, our testers started finding Simple MAPI applications which had worked fine with MS Mail but now crashed. Investigation showed that these applications were accessing memory which had been freed. Why?

Let's digress a bit and look at a typical Simple MAPI function, MAPIResolveName:

 ULONG FAR PASCAL MAPIResolveName(
    LHANDLE lhSession,
  ULONG ulUIParam,
    LPTSTR lpszName,
    FLAGS flFlags,
  ULONG ulReserved,
   lpMapiRecipDesc FAR * lppRecip )

There are two parameters of interest here: lhSession and lppRecip. lhSession can be used to pass in an existing session that was created with MAPILogon. Or it can be left NULL to let Simple MAPI create and use a temporary session for the duration of the call. lppRecip is used to pass back a structure - the resolved name. This structure is to be freed with MAPIFreeBuffer.

How would we implement this function in Extended MAPI? It would look something like this (pseudo-code):

 if (!lhSession) 
{
 lhSession = InitializeMAPIAndLogonToProfile();
  bTempSession = true;
}
DoWorkToResolveNames(lhSession,lppRecip);
if (bTempSession) LogoffProfileAndUninitializeMAPI();
return;

So far so good. And as long as all the test harnesses for Simple MAPI pass a session in whenever they call MAPIResolveName, there's no problem. But what happens when we call MAPIResolveName without a session?

It might help here to comment that MAPI handles it's own memory allocations. That's why we have a special function, MAPIFreeBuffer, for freeing memory. When MAPI is initialized, it creates a heap to handle all of it's allocations. Similarly, when MAPI is uninitialized, it destroys this heap.

I'm sure the A students are bored with this lecture so I'll wrap it up now: In the original implementation, as soon as we clean up the temporary session and uninitialize MAPI, the heap from which lppRecip was allocated is destroyed. So any attempt to use it will result in a crash.

To fix this, we were faced with two choices. Either completely rethink how memory is handled in Extended MAPI, or leak the heap whenever we uninitialize MAPI. We chose the leak. The first version of Extended MAPI we shipped back in Windows 95 contained this leak.

Jump to today: That original version of Extended MAPI/Simple MAPI eventually forked to become the basis of both Outlook and Exchange's implementations of MAPI. While both implementations have had their changes over the years, the intentional leak has remained. And as long as Simple MAPI is implemented on top of Extended MAPI, we can't take the leak out. It will even be there in Outlook 2007.

Mitigation

If you're using Outlook's MAPI, or any version of Exchange's MAPI prior to Exchange 2003, the best you can do is minimize the number of times you uninitialize MAPI. If you're using Simple MAPI, this means do an explicit MAPILogon/MAPILogoff, and don't do them in a loop. If you're using Extended MAPI, initialize MAPI on your main thread before you do any MAPI work, and wait to uninitialize it until just before your process exits. Subsequent MAPIInitialize/MAPIUninitialize calls on other threads will act like Addref/Release on the MAPI subsystem so they won't incur the leak.

If you know you'll be using MAPI from Exchange 2003 or from the MAPI download, then you can benefit from the fact that we cut Simple MAPI there. This means we were finally able to put back the code to clean up the heap. It took us a couple times to get it right, but the upshot is that once the hotfix from KB 901014 is applied, you can set the CleanUpMAPIHeap key and no longer worry about this leak.

Incidentally, the reason we wrapped this fix in a registry key is because before we took this fix, any program which got memory from MAPI, uninitialized MAPI, and then used the memory would not appear to have a problem. But like Simple MAPI, as soon as we start cleaning up the heap, this pattern will cause crashes. So do test carefully before deploying this key!