Monday, July 13, 2009

GeekSpeak: Memory Leaks in System.DirectoryServices

It rained 22 days in June (in Boston). The last few days were glorious. And I missed the sun while dealing with a memory leak.

We hit this problem while translating legacy Exchange DNs into SMTP addresses in our Exchange Room analysis tool. The culprit -- System.DirectorServices (.Net 3.5)
calls to GetDirectoryEntry().Properties. With each call to System.Directoryservices, memory use jumped by 120 bytes. The annoyance became a problem after we looked up three fields - for 8,000 users.

Microsoft's MSDN Reference says: "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.".

I did that. So did other folks posted similar problems in the MS forums. All were told to use dispose. It didn't work. After reading dozens of responses, someone said try the "using" contruct along with "dispose". I did. It worked.

For those of you who don't want to find the mines by stomping on the ground, here is sample code that shows System.DirectoryServices calls broken out into an excessive number of using blocks:

'return ONE value from AD given a filter
Public Function GetADField(byval strFilter as string, _
 byval strField as string) As String
 GetADField = ""
 Using dsDir As System.DirectoryServices.ActiveDirectory.Domain = _
    Using dsRoot As New DirectoryEntry(dsDir.Name)
      Using dsSearcher As New DirectoryServices.DirectorySearcher
        dsSearcher.SearchRoot = dsRoot
        dsSearcher.Filter = strFilter
        dsSearcher.SearchScope = SearchScope.Subtree
        dsSearcher.PropertiesToLoad.AddRange(New String() {strField})
        dsSearcher.FindAll() 'results
        Using dsResult As DirectoryServices.SearchResultCollection = _
            dsSearcher.FindAll() 'results
            Dim result As DirectoryServices.SearchResult
            For Each result In dsResult
             Using de As DirectoryEntry = result.GetDirectoryEntry()
               GetADField = de.Properties("mail").Value.ToString
             End Using 'de
           Next 'result
           result = Nothing
        End Using 'dsResult
    End Using 'dsSearcher
  End Using 'dsRoot
 End Using 'dsDir
 Return GetADField
End Function


1 comment:

Magnus said...

Excellent ! This resolved my problem !!