Re: memory leak using system.windows.forms.timer



On Feb 11, 4:56 pm, Tom Shelton <tom_shel...@xxxxxxxxxxxxxxxxxx>
wrote:
On 2009-02-11, Gestalt <sspenn...@xxxxxxxxx> wrote:



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...

--
Tom Shelton

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?

.



Relevant Pages

  • Re: memory leak using system.windows.forms.timer
    ... subroutine, memory leaks. ... Private Sub CheckComputers() ... Dim intCount As Integer ...     Private MasterTimer As System.Timers.Timer ...
    (microsoft.public.dotnet.languages.vb)
  • Re: Memory issues with Tcl_CreateCommand
    ... increase in number of arguments passed to the registered command, ... memory consumption shoots up considerably and the same is not released ...   return TCL_OK; ... Do you observe a similar leak without the eval? ...
    (comp.lang.tcl)
  • Re: memory leak using system.windows.forms.timer
    ... subroutine, memory leaks. ... Private Sub CheckComputers() ... Dim intCount As Integer ...     Private MasterTimer As System.Timers.Timer ...
    (microsoft.public.dotnet.languages.vb)
  • Re: memory leak using system.windows.forms.timer
    ... out of the Sub and put it into a module and make it Global? ... subroutine, memory leaks. ... subroutine with a static number I still leak memory. ...
    (microsoft.public.vb.general.discussion)
  • Re: Memory leak in pthread_cancel with a detached thread.
    ...   pthread_t th; ... I shouldn't have any memory leak. ... There should be no leak, but that is not connected to the fact that the ... libraries to allocate memory that valgrind has to assume freed by the ...
    (comp.unix.programmer)

Loading