by Gabe Halsmer [gabe dot halsmer at attbi dot com] posted on 2004/10/01 |
|
There is a rather large memory leak in CertificateStore's constructor.
I made an http server based on the SecureSocket class. The usual set-up. A listening socket binds to port 443 an enters an infinite loop accepting incoming requests. Each accept spawns a new private sockets to handle the request. I found out that each call to secureSocket.Receive was creating memory that never got freed. Even after the private socket was out-of-scope and garbage collected, there was about 200K of memory left in the heap. That's 200K for each http request to my server! Obviously I had to find the leak and fix or I wouldn't be able to use the SSL library.
I finally found it in the call to crypt32's CertOpenStore. Now, I'm no COM expert, but apparently this was mallocing a piece of unmanaged memory which the SecureSocket probably was to manually free but never did.
Instead of trying to figure out how to deallocate the certificate handle, I just made a static hashtable that creates one cert-store per location & store name, so I could reuse these objects in all of my sockets. I assumed the cert-store was read-only, and apparently that's true. My server works great now.
Here's my code fix...
public CertificateStore(StoreLocation location, string store)
{
if (store == null)
throw new ArgumentNullException("The name of the store cannot be a null reference.");
// GH - memory leak here!!!
// IntPtr intPtr = new IntPtr(SecurityConstants.CERT_STORE_PROV_SYSTEM_A);
// m_Handle = SspiProvider.CertOpenStore(intPtr, 0, 0, (int)location, store);
this.m_Handle = GetStoreByLocation_StoreName(location, store);
if (m_Handle == IntPtr.Zero)
throw new CertificateException("An error occurs while opening the specified store.");
}
/// <summary>
/// GH - this fixed a major leak in CertificateStore's constructor.
/// </summary>
/// <param name="location"></param>
/// <param name="store"></param>
/// <returns></returns>
private static IntPtr GetStoreByLocation_StoreName(StoreLocation location, string store)
{
if ( !storesByLocation_StoreName.ContainsKey(location) )
storesByLocation_StoreName[location] = new Hashtable();
Hashtable h = storesByLocation_StoreName[location] as Hashtable;
if (!h.ContainsKey(store))
{
IntPtr intPtr = new IntPtr(SecurityConstants.CERT_STORE_PROV_SYSTEM_A);
IntPtr handle = SspiProvider.CertOpenStore(intPtr, 0, 0, (int)location, store);
h[store] = handle;
}
return (IntPtr)h[store];
}
private static Hashtable storesByLocation_StoreName = new Hashtable();
I should note that there still is a very small memory leak in my http server. It appears to be coming from the secureSocket.Send this time. However, its small enough that I can live with it for now.
If anyone finds it, please do me a favor an e-mail me the fix. Thank you!
|
by Gabe Halsmer [gabe dot halsmer at attbi dot com] posted on 2004/10/02 |
|
Reusing the certificate store objects worked for both of my Windows 2003 Server machines (and both of them have dual CPUs, not sure that makes a difference).
But when I tried this on my Windows XP machine (single CPU), I started to get random errors. It looks like a bad case of memory corruption, because my http server would die in some really wierd places, in simple code that should obviously work.
I reverted my code back to its original state, and then it worked on my XP machine. I watched my memory usage and noted that I didn't have a memory leak problem on the XP machine at all.
So I'm guessing that the SSL library running under XP will properly destory the cert-stores created by the call to crypt32's CertOpenStore. When I applied my coding to change to reuse cert-stores, this probably caused it to use a piece of memory that had been freed. That would explain why it quickly caused my http server to crash.
However, reusing the stores on 2003 Server would never crash, even after several thousand requests. So it must not properly destory the stores along with the rest of the sockets finalizations.
|