memory leak using system.windows.forms.timer

G

Gestalt

I'm working on a small application using the
system.windows.forms.timer. The intent is to set an interval and on
the tick event fire a function that returns the number of workstation
objects that reside in a specific OU in Active Directory.

On the surface the application runs great. Under the surface, it's a
different story. I've found that every time the tick calls a trivial
subroutine, memory leaks. I've tried summoning garbage collection
periodically but that has not resulted in any improvement in memory
consumption. Left overnight the app will bloat to over half a gig of
RAM consumed (and continuing to climb). I have tested it on XP Pro,
Vista SP1 (x64) and Windows Server 2008 (x86).

The subroutine that appears to be leaking is here as follows:
Private Sub CheckComputers()
Dim intCount As Integer
intCount = CountADComputers(My.Settings.strTargetDN)
If intCount > My.Settings.intTargetCount Then
intTrigger = 1
Else
intTrigger = 0
End If
intCount = Nothing
End Sub

I have found that if I substitute the call to the CountADComputers
subroutine with a static number (e.g. 1) I still leak memory. If I
leave this subroutine out of the program then the leak stops (and
obviously the program does nothing).

For the record, here is the CountADComputers subroutine:
Public Function CountADComputers(ByVal strADPath As String) As
Integer
Dim intCount As Integer
Dim entry As New DirectoryServices.DirectoryEntry(strADPath)
Dim mySearcher As New
System.DirectoryServices.DirectorySearcher(entry)
mySearcher.PageSize = 1000
mySearcher.Filter = "(objectClass=Computer)"
intCount = mySearcher.FindAll.Count
mySearcher.Dispose()
mySearcher = Nothing
entry.Dispose()
entry = Nothing
Return intCount
End Function

Can anyone think of a reason why this would be causing a problem?
 
J

James Hahn

Copy My.Settings.intTargetCount into a static or global variable and test
that variable, to confirm that the My.Settings reference isn't the cause.
 
T

Tom Shelton

I'm working on a small application using the
system.windows.forms.timer. The intent is to set an interval and on
the tick event fire a function that returns the number of workstation
objects that reside in a specific OU in Active Directory.

On the surface the application runs great. Under the surface, it's a
different story. I've found that every time the tick calls a trivial
subroutine, memory leaks. I've tried summoning garbage collection
periodically but that has not resulted in any improvement in memory
consumption. Left overnight the app will bloat to over half a gig of
RAM consumed (and continuing to climb). I have tested it on XP Pro,
Vista SP1 (x64) and Windows Server 2008 (x86).

The subroutine that appears to be leaking is here as follows:
Private Sub CheckComputers()
Dim intCount As Integer
intCount = CountADComputers(My.Settings.strTargetDN)
If intCount > My.Settings.intTargetCount Then
intTrigger = 1
Else
intTrigger = 0
End If
intCount = Nothing
End Sub

I have found that if I substitute the call to the CountADComputers
subroutine with a static number (e.g. 1) I still leak memory. If I
leave this subroutine out of the program then the leak stops (and
obviously the program does nothing).

What else does the timer do? Only calling this CheckComputers sub? It seems
highly unlikely that simply calling a sub is going to cause a memory leak.
This generally means unreleased references..

Hmmm... intTrigger - I dont see that declared in the timer routine, so is this
being monitored somewhere? another thread? Need more info :)

By the way, setting the intCount to nothing - is basically useless in the
above, and in fact will probably optimized out of the final code anyway.
There is almost no reason to ever explicitly set any object to nothing in
VB.NET (actually this was true in VB6 as well). The major exceptions are
module and class level fields - local values, well your mostly just wasting
your time and cycles :)
For the record, here is the CountADComputers subroutine:
Public Function CountADComputers(ByVal strADPath As String) As
Integer
Dim intCount As Integer
Dim entry As New DirectoryServices.DirectoryEntry(strADPath)
Dim mySearcher As New
System.DirectoryServices.DirectorySearcher(entry)
mySearcher.PageSize = 1000
mySearcher.Filter = "(objectClass=Computer)"
intCount = mySearcher.FindAll.Count
mySearcher.Dispose()
mySearcher = Nothing
entry.Dispose()
entry = Nothing
Return intCount
End Function

If your using .NET 2.0 (2005) or higher, then I believe I would write this
routine more like:

Public Function GetADComputers (ByVal strADPath As String) As Integer
Using entry As New DirectoryServices.DirectoryEntry(strADPath), _
mySearcher As new DirectoryServices.DirectorySearcher(entry)

With mySearcher
.PageSize = 1000
.Filter = "(objectClass=Computer)"
End With

Return mySearcher.FindAll.Count
End Using
End Function

If your not using 2005, then I might suggest using Try/Finally to ensure that
dispose is called even if an exception is thrown (the above is really
syntactic sugar for Try/Finally).
Can anyone think of a reason why this would be causing a problem?

Not from the information/code provided. You might want to create a short, but
complete program (http://www.yoda.arachsys.com/csharp/complete.html) that
demostrates the issue. Or better yet, you might look at getting a memory
profiler tool - such as Ants Profiler. These kinds of tools are invaluable
when trying to track down stuborn memory leaks...
 
T

Tom Shelton

Copy My.Settings.intTargetCount into a static or global variable and test
that variable, to confirm that the My.Settings reference isn't the cause.

Intersting... I would think that this would be a wrapper around the
System.Configuarion.ConfigurationManager stuff - but who knows.
 
T

Tom Shelton

Intersting... I would think that this would be a wrapper around the
System.Configuarion.ConfigurationManager stuff - but who knows.

I'd never bothered to look... So, this probably isn't news to anyone - but,
My.Settings is actaully generated code - and is compliled in to the exe.

It's seems to be implemented as a Singleton object which appears to read
settings at startup - at least for application level settings. I'm not sure
if that is the kind of setting intTargetCount is - but, it seems an unlikely
source for a memory leak... Still, I think it's a quick and easy thing for
the OP to confirm - and who knows, may actually solve the issue ;)
 
G

Gestalt

I'd never bothered to look...  So, this probably isn't news to anyone -but,
My.Settings is actaully generated code - and is compliled in to the exe.

It's seems to be implemented as a Singleton object which appears to read
settings at startup - at least for application level settings.  I'm notsure
if that is the kind of setting intTargetCount is - but, it seems an unlikely
source for a memory leak...  Still, I think it's a quick and easy thingfor
the OP to confirm - and who knows, may actually solve the issue ;)

Thanks for the ideas! I'll test them out and post an update.
 
C

Cor Ligthert[MVP]

If your not using 2005, then I might suggest using Try/Finally to ensure
that
dispose is called even if an exception is thrown (the above is really
syntactic sugar for Try/Finally).
acetic acint
 
G

Gestalt

What else does the timer do?  Only calling this CheckComputers sub?  It seems
highly unlikely that simply calling a sub is going to cause a memory leak..
This generally means unreleased references..

Hmmm... intTrigger - I dont see that declared in the timer routine, so isthis
being monitored somewhere?  another thread? Need more info :)

By the way, setting the intCount to nothing - is basically useless in the
above, and in fact will probably optimized out of the final code anyway.
There is almost no reason to ever explicitly set any object to nothing in
VB.NET (actually this was true in VB6 as well).  The major exceptions are
module and class level fields - local values, well your mostly just wasting
your time and cycles :)




If your using .NET 2.0 (2005) or higher, then I believe I would write this
routine more like:

Public Function GetADComputers (ByVal strADPath As String) As Integer
        Using entry As New DirectoryServices.DirectoryEntry(strADPath), _
                mySearcher As new DirectoryServices.DirectorySearcher(entry)

                With mySearcher
                        .PageSize = 1000
                        .Filter = "(objectClass=Computer)"
                End With

                Return mySearcher.FindAll.Count        
        End Using
End Function

If your not using 2005, then I might suggest using Try/Finally to ensure that
dispose is called even if an exception is thrown (the above is really
syntactic sugar for Try/Finally).




Not from the information/code provided.  You might want to create a short, but
complete program (http://www.yoda.arachsys.com/csharp/complete.html) that
demostrates the issue.  Or better yet, you might look at getting a memory
profiler tool  - such as Ants Profiler.  These kinds of tools are invaluable
when trying to track down stuborn memory leaks...

I've tried your suggestions and narrowed things down a bit.

First, I was wrong about the AD query not being the culprit. It is
the source of the leak. It turns out that my copy of Visual Studio
isn't always completing the build process when I tell it to do so.
Thus when I thought I was testing the code with AD query commented out
it was in fact still part of the cycle. I'm doing my dev from another
install now and the builds are coming out correctly.

I am evaluating the Ants Profiler app and I think it has identified
the source of the problem. Every time the Directory Searcher returns
new results, those results remain in memory even when the searcher is
disposed of. Garbage collection does not clean up the data, even when
called manually or through the Ants Profiler interface.

The only way I have found to mitigate this problem is to refine the
scope of my search to only return a single attribute from the computer
objects. This has slowed the growth in memory considerably but it has
by no means stopped it. I ran a test app overnight and it went from
7Meg RAM at start to 160Meg.

Here is a demonstrative app illustrating the problem. To test it an
active directory domain will need to be provided.

Imports System.Timers

Module Module1
Private MasterTimer As System.Timers.Timer
Dim strPath As String = "LDAP://<your domain here>"
Dim intTrigger As Integer = 0

Sub Main()
MasterTimer = New System.Timers.Timer(500)
AddHandler MasterTimer.Elapsed, AddressOf Tick
MasterTimer.Enabled = True
Console.WriteLine("Press the Enter key to exit the program.")
Console.ReadLine()
End Sub

Private Sub Tick(ByVal source As Object, ByVal e As
ElapsedEventArgs)
CheckComputers()
End Sub

Private Sub CheckComputers()
If GetADComputers(strPath) > 1 Then
Console.WriteLine("Alert State Detected: " & GetADComputers
(strPath) & vbCrLf)
intTrigger = 1
Else
Console.WriteLine("Situation Normal " & GetADComputers
(strPath) & vbCrLf)
intTrigger = 0
End If
End Sub

Public Function GetADComputers(ByVal strADPath As String) As
Integer
Using entry As New DirectoryServices.DirectoryEntry
(strADPath), mySearcher As New DirectoryServices.DirectorySearcher
(entry)
With mySearcher
.PageSize = 1000
.Filter = "(objectClass=Computer)"
.PropertiesToLoad.Add("sAMAccountName")
End With
Return mySearcher.FindAll.Count
End Using
End Function
End Module

Does anyone have any idea what I can do to stop the leak entirely?
Should I address this to the ADO group?
 
C

Cor Ligthert[MVP]

Gestalt,

As you where giving typical problems as there are often with
system.timers.timer,my first gues was that the problem was in that.

However the subject says clearly system.windows.forms.timer

However, in your code is the system.timer.timer

(Are you busy with a service, because that is for what the where the
system.timers.timer is.

Cor

What else does the timer do? Only calling this CheckComputers sub? It
seems
highly unlikely that simply calling a sub is going to cause a memory leak.
This generally means unreleased references..

Hmmm... intTrigger - I dont see that declared in the timer routine, so is
this
being monitored somewhere? another thread? Need more info :)

By the way, setting the intCount to nothing - is basically useless in the
above, and in fact will probably optimized out of the final code anyway.
There is almost no reason to ever explicitly set any object to nothing in
VB.NET (actually this was true in VB6 as well). The major exceptions are
module and class level fields - local values, well your mostly just
wasting
your time and cycles :)




If your using .NET 2.0 (2005) or higher, then I believe I would write this
routine more like:

Public Function GetADComputers (ByVal strADPath As String) As Integer
Using entry As New DirectoryServices.DirectoryEntry(strADPath), _
mySearcher As new DirectoryServices.DirectorySearcher(entry)

With mySearcher
.PageSize = 1000
.Filter = "(objectClass=Computer)"
End With

Return mySearcher.FindAll.Count
End Using
End Function

If your not using 2005, then I might suggest using Try/Finally to ensure
that
dispose is called even if an exception is thrown (the above is really
syntactic sugar for Try/Finally).




Not from the information/code provided. You might want to create a short,
but
complete program (http://www.yoda.arachsys.com/csharp/complete.html) that
demostrates the issue. Or better yet, you might look at getting a memory
profiler tool - such as Ants Profiler. These kinds of tools are invaluable
when trying to track down stuborn memory leaks...

I've tried your suggestions and narrowed things down a bit.

First, I was wrong about the AD query not being the culprit. It is
the source of the leak. It turns out that my copy of Visual Studio
isn't always completing the build process when I tell it to do so.
Thus when I thought I was testing the code with AD query commented out
it was in fact still part of the cycle. I'm doing my dev from another
install now and the builds are coming out correctly.

I am evaluating the Ants Profiler app and I think it has identified
the source of the problem. Every time the Directory Searcher returns
new results, those results remain in memory even when the searcher is
disposed of. Garbage collection does not clean up the data, even when
called manually or through the Ants Profiler interface.

The only way I have found to mitigate this problem is to refine the
scope of my search to only return a single attribute from the computer
objects. This has slowed the growth in memory considerably but it has
by no means stopped it. I ran a test app overnight and it went from
7Meg RAM at start to 160Meg.

Here is a demonstrative app illustrating the problem. To test it an
active directory domain will need to be provided.

Imports System.Timers

Module Module1
Private MasterTimer As System.Timers.Timer
Dim strPath As String = "LDAP://<your domain here>"
Dim intTrigger As Integer = 0

Sub Main()
MasterTimer = New System.Timers.Timer(500)
AddHandler MasterTimer.Elapsed, AddressOf Tick
MasterTimer.Enabled = True
Console.WriteLine("Press the Enter key to exit the program.")
Console.ReadLine()
End Sub

Private Sub Tick(ByVal source As Object, ByVal e As
ElapsedEventArgs)
CheckComputers()
End Sub

Private Sub CheckComputers()
If GetADComputers(strPath) > 1 Then
Console.WriteLine("Alert State Detected: " & GetADComputers
(strPath) & vbCrLf)
intTrigger = 1
Else
Console.WriteLine("Situation Normal " & GetADComputers
(strPath) & vbCrLf)
intTrigger = 0
End If
End Sub

Public Function GetADComputers(ByVal strADPath As String) As
Integer
Using entry As New DirectoryServices.DirectoryEntry
(strADPath), mySearcher As New DirectoryServices.DirectorySearcher
(entry)
With mySearcher
.PageSize = 1000
.Filter = "(objectClass=Computer)"
.PropertiesToLoad.Add("sAMAccountName")
End With
Return mySearcher.FindAll.Count
End Using
End Function
End Module

Does anyone have any idea what I can do to stop the leak entirely?
Should I address this to the ADO group?
 
G

Gestalt

Gestalt,

As you where giving typical problems as there are often with
system.timers.timer,my first gues was that the problem was in that.

However the subject says clearly system.windows.forms.timer

However, in your code is the system.timer.timer

(Are you busy with a service, because that is for what the where the
system.timers.timer is.

Cor












I've tried your suggestions and narrowed things down a bit.

First, I was wrong about the AD query not being the culprit.  It is
the source of the leak.  It turns out that my copy of Visual Studio
isn't always completing the build process when I tell it to do so.
Thus when I thought I was testing the code with AD query commented out
it was in fact still part of the cycle.  I'm doing my dev from another
install now and the builds are coming out correctly.

I am evaluating the Ants Profiler app and I think it has identified
the source of the problem.  Every time the Directory Searcher returns
new results, those results remain in memory even when the searcher is
disposed of.  Garbage collection does not clean up the data, even when
called manually or through the Ants Profiler interface.

The only way I have found to mitigate this problem is to refine the
scope of my search to only return a single attribute from the computer
objects.  This has slowed the growth in memory considerably but it has
by no means stopped it.  I ran a test app overnight and it went from
7Meg RAM at start to 160Meg.

Here is a demonstrative app illustrating the problem.  To test it an
active directory domain will need to be provided.

Imports System.Timers

Module Module1
    Private MasterTimer As System.Timers.Timer
    Dim strPath As String = "LDAP://<your domain here>"
    Dim intTrigger As Integer = 0

    Sub Main()
        MasterTimer = New System.Timers.Timer(500)
        AddHandler MasterTimer.Elapsed, AddressOf Tick
        MasterTimer.Enabled = True
        Console.WriteLine("Press the Enter key to exit the program.")
        Console.ReadLine()
    End Sub

    Private Sub Tick(ByVal source As Object, ByVal e As
ElapsedEventArgs)
        CheckComputers()
    End Sub

    Private Sub CheckComputers()
        If GetADComputers(strPath) > 1 Then
            Console.WriteLine("Alert State Detected: " & GetADComputers
(strPath) & vbCrLf)
            intTrigger = 1
        Else
            Console.WriteLine("Situation Normal " & GetADComputers
(strPath) & vbCrLf)
            intTrigger = 0
        End If
    End Sub

    Public Function GetADComputers(ByVal strADPath As String) As
Integer
        Using entry As New DirectoryServices.DirectoryEntry
(strADPath), mySearcher As New DirectoryServices.DirectorySearcher
(entry)
            With mySearcher
                .PageSize = 1000
                .Filter = "(objectClass=Computer)"
                .PropertiesToLoad.Add("sAMAccountName")
            End With
            Return mySearcher.FindAll.Count
        End Using
    End Function
End Module

Does anyone have any idea what I can do to stop the leak entirely?
Should I address this to the ADO group?

You are correct, I have shifted from system.windows.forms.timer to
system.timers.timer for the purposes of illustrating the problem. It
was suggested that I post a simple code example illustrating the
problem. It appears that the problem occurs using either timer. The
key issue appears to be that garbage collection is not cleaning up the
results retrieved by the AD search contained in the GetADComputers
function. I'll try some additional tests using the same function
repeatedly without benefit of a timer and see if the results still
build up in memory.

Any other additional suggestions are greatly appreciated.
 
G

Gestalt

You are correct, I have shifted from system.windows.forms.timer to
system.timers.timer for the purposes of illustrating the problem.  It
was suggested that I post a simple code example illustrating the
problem.  It appears that the problem occurs using either timer.  The
key issue appears to be that garbage collection is not cleaning up the
results retrieved by the AD search contained in the GetADComputers
function.  I'll try some additional tests using the same function
repeatedly without benefit of a timer and see if the results still
build up in memory.

Any other additional suggestions are greatly appreciated.
Here is the latest test app. No timers, just a GUI button to call the
AD query. The leak is definitely in the AD search and not in the
timer.

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button1.Click
Label1.Text = GetADComputers("LDAP://<your domain here>")
End Sub

Public Function GetADComputers(ByVal strADPath As String) As Integer
Using entry As New DirectoryServices.DirectoryEntry(strADPath),
mySearcher As New DirectoryServices.DirectorySearcher(entry)
With mySearcher
.PageSize = 1000
.Filter = "(objectClass=Computer)"
End With
Return mySearcher.FindAll.Count
End Using
End Function

Using the Windows Task Manager, I can watch the memory consumed by the
application go up each time the button is pressed. Garbage collection
does not appear to have any impact.

I've used the Ants Profiler to peek into the RAM data and it appears
to be filled with the results from the AD search. I can mitigate this
by reducing the attributes returned since I am only looking for a
headcount but this really only slows the problem.

Is there any way I can flush these results from memory?
 
M

Michel Posseth [MCP]

I have almost the same code as you use in one of my business objects , i
encapsulated everything in a single class
and it does seem to work fine . However my code isn`t called repeatedly so i
will investigate what will happen when i do this when i am at work ( it is
here 6:38 AM at time of writing ).

If it does behave as you describe, i will put the code in a seperate
application domain and just unload it after usage , this should free all
memory currently hold by that part of the process ( i recently showed an
example in this groups how to do that ).


HTH

Michel




"Gestalt" <[email protected]> schreef in bericht
You are correct, I have shifted from system.windows.forms.timer to
system.timers.timer for the purposes of illustrating the problem. It
was suggested that I post a simple code example illustrating the
problem. It appears that the problem occurs using either timer. The
key issue appears to be that garbage collection is not cleaning up the
results retrieved by the AD search contained in the GetADComputers
function. I'll try some additional tests using the same function
repeatedly without benefit of a timer and see if the results still
build up in memory.

Any other additional suggestions are greatly appreciated.
Here is the latest test app. No timers, just a GUI button to call the
AD query. The leak is definitely in the AD search and not in the
timer.

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button1.Click
Label1.Text = GetADComputers("LDAP://<your domain here>")
End Sub

Public Function GetADComputers(ByVal strADPath As String) As Integer
Using entry As New DirectoryServices.DirectoryEntry(strADPath),
mySearcher As New DirectoryServices.DirectorySearcher(entry)
With mySearcher
.PageSize = 1000
.Filter = "(objectClass=Computer)"
End With
Return mySearcher.FindAll.Count
End Using
End Function

Using the Windows Task Manager, I can watch the memory consumed by the
application go up each time the button is pressed. Garbage collection
does not appear to have any impact.

I've used the Ants Profiler to peek into the RAM data and it appears
to be filled with the results from the AD search. I can mitigate this
by reducing the attributes returned since I am only looking for a
headcount but this really only slows the problem.

Is there any way I can flush these results from memory?
 
G

Gestalt

I have almost the same code as you use in one of my business objects , i
encapsulated everything in a single class
and it does seem to work fine . However my code isn`t called repeatedly so i
will investigate what will happen when i do this when i am at work ( it is
here 6:38 AM at time of writing  ).

If it does behave as you describe,  i will put the code in a seperate
application domain and just unload it after usage , this should free all
memory currently hold by that part of the process ( i recently showed an
example in this groups how to do that ).

HTH

Michel

"Gestalt" <[email protected]> schreef in bericht



Here is the latest test app.  No timers, just a GUI button to call the
AD query.  The leak is definitely in the AD search and not in the
timer.

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button1.Click
   Label1.Text = GetADComputers("LDAP://<your domain here>")
End Sub

Public Function GetADComputers(ByVal strADPath As String) As Integer
   Using entry As New DirectoryServices.DirectoryEntry(strADPath),
mySearcher As New DirectoryServices.DirectorySearcher(entry)
       With mySearcher
           .PageSize = 1000
           .Filter = "(objectClass=Computer)"
       End With
       Return mySearcher.FindAll.Count
   End Using
End Function

Using the Windows Task Manager, I can watch the memory consumed by the
application go up each time the button is pressed.  Garbage collection
does not appear to have any impact.

I've used the Ants Profiler to peek into the RAM data and it appears
to ...

read more »

That makes sense, I will give that a try. I'd been thinking of
shifting my AD search to a separate thread to try encapsulating the
results but your solution sounds like a more eloquent one. I'll for
your example. Thanks!
 
M

Michel Posseth [MCP]

Hello ,,

Well i digged in a bit deeper in your problem as i tested my previous hint
( running the code in a seperate app domain and then unload it )
this did not solve the issue

To my own suprise the code bloated up the memory annyway an was not
releasing it even after calling GC.Collect etc etc

< TEST 1>

For i = 0 To 500
Dim ad As AppDomain = AppDomain.CreateDomain("VBDC_new_domain", Nothing,
Nothing)

Dim oh As ObjectHandle = ad.CreateInstance("MPTest", "MPTest.AppDomainAD")

Dim adInfo As AppDomainAD = DirectCast((oh.Unwrap()), AppDomainAD)

Label1.Text = CStr(adInfo.GetADComputers("LDAP://NL"))

AppDomain.Unload(ad)

Try

Console.WriteLine()

Console.WriteLine(("Host domain: " + AppDomain.CurrentDomain.FriendlyName))

' The following statement creates an exception because the domain no longer
exists.

Console.WriteLine(("child domain: " + ad.FriendlyName))

Catch ex As AppDomainUnloadedException

Console.WriteLine("The appdomain MyDomain does not exist.")

End Try

Console.WriteLine("Application working set " +
My.Application.Info.WorkingSet.ToString)

Next

Public Class AppDomainAD

Inherits MarshalByRefObject

Public Function GetADComputers(ByVal strADPath As String) As Integer

Dim retval As Integer = 0

Using entry As New DirectoryServices.DirectoryEntry(strADPath), mySearcher
As New DirectoryServices.DirectorySearcher(entry)

With mySearcher

..PageSize = 1000

..Filter = "(objectClass=Computer)"

End With

entry.Close()

retval = mySearcher.FindAll.Count

mySearcher.Dispose()

End Using

Return retval

End Function

End Class

< / TEST 1>

The above code should have worked , however it did not :-( , but as i am a
coder who never faills , i have just found 10.000 ways that won`t work :)
i had at one point a eureka moment we are dealing here with a unmanaged
memory leak !! .

So after some tracing i found the cause and solution
< TEST 2>
Public Function GetADComputers(ByVal strADPath As String) As Integer

Dim retval As Integer = 0

Using entry As New DirectoryServices.DirectoryEntry(strADPath)

Using mySearcher As New DirectoryServices.DirectorySearcher(entry)

With mySearcher

..PageSize = 1000

..Filter = "(objectClass=Computer)"

End With

Using returnValue As DirectoryServices.SearchResultCollection =
mySearcher.FindAll

retval = returnValue.Count

End Using

End Using

entry.Close()

End Using

Return retval

End Function


< TEST 2>

The above code works flawless in my tests with iteration of 5000 it took +-
30 mb on my workstation
the SearchResultCollection can obviously not free it`s unmanaged resources
so by splitting it in a seprate using block ( or by manually calling
dispose )
it will returns its resources .

The above case is a perfect example of a discussion we had here multiple
times that when a object that exposes a close and / or dispose method these
should be called when finished with it especially when they are wrappers
around unmanaged resources as in this case this is a must as this is a case
where the framework doesn`t save you :) .

HTH

And regards

Michel Posseth [MCP]
http://www.linkedin.com/in/michelposseth







"Gestalt" <[email protected]> schreef in bericht
I have almost the same code as you use in one of my business objects , i
encapsulated everything in a single class
and it does seem to work fine . However my code isn`t called repeatedly so
i
will investigate what will happen when i do this when i am at work ( it is
here 6:38 AM at time of writing ).

If it does behave as you describe, i will put the code in a seperate
application domain and just unload it after usage , this should free all
memory currently hold by that part of the process ( i recently showed an
example in this groups how to do that ).

HTH

Michel

"Gestalt" <[email protected]> schreef in
bericht



Here is the latest test app. No timers, just a GUI button to call the
AD query. The leak is definitely in the AD search and not in the
timer.

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button1.Click
Label1.Text = GetADComputers("LDAP://<your domain here>")
End Sub

Public Function GetADComputers(ByVal strADPath As String) As Integer
Using entry As New DirectoryServices.DirectoryEntry(strADPath),
mySearcher As New DirectoryServices.DirectorySearcher(entry)
With mySearcher
.PageSize = 1000
.Filter = "(objectClass=Computer)"
End With
Return mySearcher.FindAll.Count
End Using
End Function

Using the Windows Task Manager, I can watch the memory consumed by the
application go up each time the button is pressed. Garbage collection
does not appear to have any impact.

I've used the Ants Profiler to peek into the RAM data and it appears
to ...

read more »

That makes sense, I will give that a try. I'd been thinking of
shifting my AD search to a separate thread to try encapsulating the
results but your solution sounds like a more eloquent one. I'll for
your example. Thanks!
 
G

Gestalt

Hello ,,

Well i digged in a bit deeper in your problem as i  tested my previous hint
( running the code in a seperate app domain and then unload it )
this did not solve the issue

To my own suprise the code bloated up the memory annyway an was not
releasing it even after calling GC.Collect etc etc

< TEST 1>

For i = 0 To 500
Dim ad As AppDomain = AppDomain.CreateDomain("VBDC_new_domain", Nothing,
Nothing)

Dim oh As ObjectHandle = ad.CreateInstance("MPTest", "MPTest.AppDomainAD")

Dim adInfo As AppDomainAD = DirectCast((oh.Unwrap()), AppDomainAD)

Label1.Text = CStr(adInfo.GetADComputers("LDAP://NL"))

AppDomain.Unload(ad)

Try

Console.WriteLine()

Console.WriteLine(("Host domain: " + AppDomain.CurrentDomain.FriendlyName))

' The following statement creates an exception because the domain no longer
exists.

Console.WriteLine(("child domain: " + ad.FriendlyName))

Catch ex As AppDomainUnloadedException

Console.WriteLine("The appdomain MyDomain does not exist.")

End Try

Console.WriteLine("Application working set " +
My.Application.Info.WorkingSet.ToString)

Next

Public Class AppDomainAD

Inherits MarshalByRefObject

Public Function GetADComputers(ByVal strADPath As String) As Integer

Dim retval As Integer = 0

Using entry As New DirectoryServices.DirectoryEntry(strADPath), mySearcher
As New DirectoryServices.DirectorySearcher(entry)

With mySearcher

.PageSize = 1000

.Filter = "(objectClass=Computer)"

End With

entry.Close()

retval = mySearcher.FindAll.Count

mySearcher.Dispose()

End Using

Return retval

End Function

End Class

< / TEST 1>

The above code should have worked , however it did not :-( , but as i am a
coder who never faills ,  i have just found 10.000 ways that won`t work:)
i had at one point a eureka moment we are dealing here with a unmanaged
memory leak !! .

So after some tracing i found the cause and solution
< TEST 2>
Public Function GetADComputers(ByVal strADPath As String) As Integer

Dim retval As Integer = 0

Using entry As New DirectoryServices.DirectoryEntry(strADPath)

Using mySearcher As New DirectoryServices.DirectorySearcher(entry)

With mySearcher

.PageSize = 1000

.Filter = "(objectClass=Computer)"

End With

Using returnValue As DirectoryServices.SearchResultCollection =
mySearcher.FindAll

retval = returnValue.Count

End Using

End Using

entry.Close()

End Using

Return retval

End Function

< TEST 2>

The above code works flawless in my tests with iteration of 5000 it took +-
30 mb on my workstation
the SearchResultCollection can obviously  not free it`s unmanaged resources
so by splitting it in a seprate using block ( or by manually calling
dispose )
it will returns its resources .

The above case is a perfect example of a discussion we had here multiple
times that when a object that exposes a close and  / or dispose method these
should be called when finished with it   especially when they are wrappers
around unmanaged resources as in this case this is a must as this is a case
where the framework doesn`t save you  :)  .

HTH

And regards

Michel Posseth [MCP]http://www.linkedin.com/in/michelposseth

"Gestalt" <[email protected]> schreef in bericht
I have almost the same code as you use in one of my business objects , i
encapsulated everything in a single class
and it does seem to work fine . However my code isn`t called repeatedlyso
i
will investigate what will happen when i do this when i am at work ( itis
here 6:38 AM at time of writing ).
If it does behave as you describe, i will put the code in a seperate
application domain and just unload it after usage , this should free all
memory currently hold by that part of the process ( i recently showed an
example in this groups how to do that ).


"Gestalt" <[email protected]> schreef in
bericht

...

read more »

Thank you!
This works perfectly!
Your solution also sheds light on how to fix a few other AD related
apps that are also showing memory problems.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top