I have a long running process that needs to do a lot of queries on Active Directory quite often. For this purpose I have been using the System.DirectoryServices namespace, using the DirectorySearcher and DirectoryEntry classes. I have noticed a memory leak in the application.
It can be reproduced with this code:
while (true)
{
using (var de = new DirectoryEntry("LDAP://hostname", "user", "pass"))
{
using (var mySearcher = new DirectorySearcher(de))
{
mySearcher.Filter = "(objectClass=domain)";
using (SearchResultCollection src = mySearcher.FindAll())
{
}
}
}
}
The documentation for these classes say that they will leak memory if Dispose() is not called. I have tried without dispose as well, it just leaks more memory in that case. I have tested this with both framework versions 2.0 and 4.0 Has anyone run into this before? Are there any workarounds?
Update: I tried running the code in another AppDomain, and it didn't seem to help either.
As strange as it may be, it seems that the memory leak only occurs if you don't do anything with the search results. Modifying the code in the question as follows does not leak any memory:
This seems to be caused by the internal searchObject field having lazy initialization , looking at SearchResultCollection with Reflector :
The dispose will not close the unmanaged handle unless searchObject is initialized.
Calling MoveNext on the ResultsEnumerator calls the SearchObject on the collection thus making sure it is disposed properly as well.
The leak in my application was due to some other unmanaged buffer not being released properly and the test I made was misleading. The issue is resolved now.
Have you tried
using
andDispose()
? Info from hereUpdate
Try calling
de.Close();
before the end of the using.I don't actually have an Active Domain Service to test this on, sorry.
Found a quick and dirty way around this.
I had a similar issue in my program with memory growth but by changing .GetDirectoryEntry().Properties("cn").Value to
.GetDirectoryEntry().Properties("cn").Value.ToString with a if before hand to make sure .value was not null
i was able to tell GC.Collect to get rid of the temporary value in my foreach. It looks like the .value was actually keeping the object alive rather then allowing it to be collected.
The managed wrapper doesn't really leak anything. If you don't call
Dispose
unused resources will still be reclaimed during garbage collection.However, the managed code is a wrapper on top of the COM-based ADSI API and when you create a
DirectoryEntry
the underlying code will call theADsOpenObject
function. The returned COM object is released when theDirectoryEntry
is disposed or during finalization.There is a documented memory leak when you use the ADsOpenObject API together with a set of credentials and a WinNT provider:
However, the leak is only 8 bytes and and as far as I can see you are using the LDAP provider and not the WinNT provider.
Calling
DirectorySearcher.FindAll
will perform a search that requires considerable cleanup. This cleanup is done inDirectorySearcher.Dispose
. In your code this cleanup is performed in each iteration of the loop and not during garbage collection.Unless there really is an undocumented memory leak in the LDAP ADSI API the only explanation I can come up with is fragmentation of the unmanaged heap. The ADSI API is implemented by an in-process COM server and each search will probably allocate some memory on the unmanaged heap of your process. If this memory becomes fragmented the heap may have to grow when space is allocated for new searches.
If my hypothesis is true, one option would be to run the searches in a separate AppDomain that then can be reclaimed to unload ADSI and recycle the memory. However, even though memory fragmentation may increase the demand for unmanaged memory I would expect that there would be an upper limit to how much unmanaged memory is required. Unless of course you have a leak.
Also, you could try to play around with the
DirectorySearcher.CacheResults
property. Does setting it tofalse
remove the leak?Due to implementation restrictions, the SearchResultCollection class cannot release all of its unmanaged resources when it is garbage collected. To prevent a memory leak, you must call the Dispose method when the SearchResultCollection object is no longer needed.
http://msdn.microsoft.com/en-us/library/system.directoryservices.directorysearcher.findall.aspx
EDIT:
I've been able to repro the apparent leak using perfmon, and adding a counter for Private Bytes on the process name of the test app (Experiments.vshost for me )
the Private Bytes counter will steadily grow while the app is looping, it starts around 40,000,000, and then grows by about a million bytes every few seconds. The good news is the counter drops back to normal (35,237,888) when you terminate the app, so some sort of cleanup is finally occurring then.
I've attached a screen shot of what perfmon looks like when its leaking
Update:
I've tried a few workarounds, like disabling caching on the DirectoryServer object, and it didn't help.
The FindOne() command doesn't leak memory, but i'm not sure what you would have to do to make that option work for you, probably edit the filter constantly, on my AD controller, there is just a single domain, so findall & findone give the same result.
I also tried queuing 10,000 threadpool workers to make the same DirectorySearcher.FindAll(). It finished alot faster, however it still leaked memory, and actually private bytes went up to about 80MB, instead of just 48MB for the "normal" leak.
So for this issue, if you can make FindOne() work for you, you have a workaround. Good Luck!