Solved

Class_Terminate not firing

Posted on 2004-10-26
1,397 Views
Last Modified: 2008-02-01
Hi experts

I have a custom class module which I instatiate serverl hundred times in a loop as I iterate through a recordset. The problem I'm having is that access is running out of 'databases' or 'tables' as its whim takes it.

Someone suggested in another forum that this could be related custom objects not being released. I have one class, which has its properties serveral other objects (class modules instantiated).  I explicity release this object by setting it =nothing, but alarm bells are ringing now that I notice that it  doesn't execute the terminate method. Can anyone suggest what could be the possible causes of this?


Here's an illustration of what I'm talking about:

Class Fleas:
-----------------------
Public Property Get HowManyFleas()
    HowManyFleas = "lots and lots"
End Property


Private Sub Class_Initialize()
    Debug.Print " New set of fleas"
End Sub

Private Sub Class_Terminate()
    Debug.Print "No more fleas"
End Sub
------------------------------------------

Class Dog:
------------------------------------------
Dim MyFleas
Private Sub Class_Initialize()
    Debug.Print "A dog is born"
    Set MyFleas = New Fleas
   
End Sub

Function Eat()
    Debug.Print "ymym arghymm aslkjs"
End Function

Public Property Get Fleas()
    Set Fleas = MyFleas
End Property

Function Bark()
    Debug.Print "woof"
End Function


Private Sub Class_Terminate()
    Debug.Print " A dog dies"
    set MyFleas = nothing
End Sub
------------------------------------------

Here's a possible session from the immediate window and what is produced:
________________________________________________________________
set MyDog = new dog
A dog is born
 New set of fleas
debug.? MyDog.eat
arghymm aslkjs

debug.? MyDog.Bark
woof

debug.? MyDog.Fleas.HowManyFleas
lots and lots
Set MyDog=nothing
 A dog dies
No more fleas
-------------------------------------------------------------------------------------------

As you can see after setting MyDog = nothing the sub Class_Terminate()  for dog executed and similarly so did the Class_Terminate for fleas.
However this is not happening in my (much more elaborate) real world scenario and so I fear much gargabe isn't  being collected and access is eventually producing an error as a result.

Any ideas what it takes to stop Class_Terminate() executing?
I've made this urgent at 500 points because a) its damned tricky and no-ones had a crack at my original attempt to get this answered & b) I need it urgently
Glenn
0
Question by:valhallatech
    10 Comments
     
    LVL 58

    Assisted Solution

    by:harfang
    I had this case once.

    For starters, let me share a little trick.

    Create a form, i called it zsfmsgOpen, with only a short window title bar:
    width about 1.5 inches, detail section height = 0, no buttons, thin border, popup.
    Really just a small rectangle, saved at the top left.

    Here is the module:

    '———————————————————————————————————————————————————————————————————————————————
    ' Form zsfmsgOpen
    '———————————————————————————————————————————————————————————————————————————————
    '
    ' This is used as a trace of an existing object in memory. An instance of the
    ' form is created and the .Caption can be used for basic trace information.
    ' When the object is released from memory, the form disapears. If it doesn't,
    ' we know that the object is still present.
    ' In Access, this happens with cross-references objects!
    '
    Option Compare Database
    Option Explicit

    Private Sub Form_Open(Cancel As Integer)
    On Error Resume Next
        DoCmd.MoveSize Rnd * 1000, Rnd * 3000
        Me.Visible = True
    End Sub


    Now you can paste the following in your class modules:

    #If DEBUGGING > 12 Then
    Dim frmDEBUG            As New Form_zsfmsgOpen      ' a visible trace of Me :)
    #End If

    Private Sub Class_Initialize()
    #If DEBUGGING > 12 Then
        frmDEBUG.Caption = TypeName(Me)
    #End If
    End Sub


    The cool thing is that a new instance of a form is created when the class is instanciated (made visible automatically) and that it also dissapears automatically.
    Now you have a permanent memory trace, cool to watch, too :)


    In my case, the problem came from cross references. objA had a variable pointing to objB, itself pointing back to objA... Of course, these objects exist only as long as their holding variables exist. It is not enough to say:

       set objA = nothing
       set objB = nothing

    They still exist because they have internal variables pointing to one another, thus happily living in their own universe. Instead:

       set objA.parent = nothing
       set objA = nothing
       set objB.child = nothing
       set objB = nothing

    I truly understood the bug a year later, reading a description of the Java garbage collector, specifically written to detect these cases.

    Hope this helps
    0
     
    LVL 58

    Expert Comment

    by:harfang
    hehe

    In your excellent examples, add to the general section of Fleas:

       Public Master As Dogs

    In Class_Initialize of Dogs, add:

       MyFleas.Master = Me

    And run the debug window code again... (more fun with my zsfmsgOpen trick, though)
    0
     
    LVL 16

    Accepted Solution

    by:
    A tricky thing to look out for is cyclic references.  Garbage collection does not happen until all references to an object are vanquished.  If the object object you think you've dereferenced ("nothinged") actually contains references to child objects that contain references back to it... garbage collection won't pick it up.
     
    For instance, add a Dog property to Fleas, and set it in the Dog class's initialisation.
    IE:
        Private Sub Class_Initialize()
            Debug.Print "A dog is born"
            Set MyFleas = New Fleas
            Set MyFleas.Dog = Me  
        End Sub

    Now any Dog object points to it's Myfleas and it's MyFleas points back to it, so dereferencing the dog object in your code will not actually delete all the references to it! Thus the class terminator will not be triggered.  
     
    And this is an easy case to spot, it can get a lot more convoluted the more hidden objects and collections get involved in a data structure.

    This is why a lot of complex classes contain a Close or Quit method which explicitly clears out all of their references (often Closing them in turn as well) rather than leaving it up to class Terminate and trusting the garbage collector.  You'll notice that a lot of DAO objects, such as Recordset, and Database, have a Close method.  You should use it before you dereference these objects, just in case.
     
    Even when cyclic references aren't present, garbage collection does not happen immediately after you dereference something.  The job is put on a todo que with a rather low priority, and if you've asked the program to process a lot of code, the cleanup can be delayed longer than you might expect.  (Heck, sometimes it can be delayed when you've asked it to do next to nothing!)  It's a matter of VB task prioritisation, something of which you have no control over - unless you explicitly close things.

    A good example might be if you use a lot of CurrentDb calls, as this may be a source of memory leakage.  CurrentDb() opens an new Database object /each/ time it is called, and if you are not referencing them, these anomynous objects may not close properly; thus garbare collection may be missing them.  

    Here's how to use CurrentDb safely:

      Dim Db as DAO.Database
      Set Db = CurrentDb
         '<--do things with Db -->
         Db.Close
      Set Db=Nothing

    0
     
    LVL 16

    Expert Comment

    by:GreymanMSC
    Damn...must...type...faster...

    :)
    0
     
    LVL 58

    Expert Comment

    by:harfang
    Sorry Greyman :)

    But the speech about adding a .Close method is excellent. This was in fact the solution I ended up with and I should have mentioned it here as well.
    0
     
    LVL 16

    Expert Comment

    by:GreymanMSC
    It's all good, harfang.  I do like that memory trace trick of yours.  Conjuring form instances is a rather inefficient way of tracing memory leaks, but exceedingly fun!
    0
     
    LVL 2

    Author Comment

    by:valhallatech
    thx for both inputs - I won't get a chance to test the ideas for another 24 hrs or so :-(
    I've suspected its the cross referencing as there is much pointing back to parents - I had a trivial attempt at addressing this and gave up as it didn't look like it would work so I abandoned it.

    The forms as a 'mirror' of whats in memory is a neat idea - I'll play with that  - at the moment I just record a random key for each object in a table and note update the status of that on initalise and terminate - the form though will provide a reflection of what -is- in memory not what should be.

    I think the close method is very sound and will have to become part of my standard coding practice I think.

    The only concern I have with both observations is that the class_terminate method didn't even try to execute - I had debug.? and break points in there and the program flow just didn't even execute the method, like the terminate event never occured. I'm hoping the close method notion will make this  immaterial

    THanks both for your input thus far - I've been pulling out what remaining hair I have over this
    Glenn
    0
     
    LVL 58

    Expert Comment

    by:harfang
    > The only concern I have with both observations is that the class_terminate method didn't even try to execute

    You seem to assume that

       Set MyDog = Nothing

    is somhow equivalent to

       MyDog.Class_Terminate()

    This is not the case. VB decides how and when to use the Class_Terminate handler for each object. To simplify things, imagine that each instance of a class has a hidden property called CountRefs.
    When you use Set ObjectRef = New claAny, the object is created, Class_initialize is executed and CountRefs is set to 1.
    When you use Set MyVar = ObjectRef, CountRefs is increased by one.
    When you use Set MyVar = SomethingElse, CountRefs is decreased.
    When you use Set MyVar = Nothing, likewise (if it referenced the object before)
    When MyVar is removed (goes out of scope), likewise.
    Whenever CountRefs drops to 0, Class_Terminate is executed and the memory is reclaimed...
    If CountRefs never drops to 0 -- and if you no longer have access to the variable pointing to the object... Class_Terminate is never executed

    It is  probably a little more complex than that, but everything *happens* as if it was!

    Finally, do not assume that the .Close method will somehow trigger the Class_Terminate event. The .Close method will simply make sure that you set all object variables to Nothing, which will at some point drop the referenced objet's "CountRefs" to 0 and allow VB to close *those*

    Cheers!
    0
     
    LVL 2

    Author Comment

    by:valhallatech
    >You seem to assume that
    >Set MyDog = Nothing
    >is somhow equivalent to
    >   MyDog.Class_Terminate()

    I didn't assume they were synonmous, but did assume one caused the other - your CountRef story above says it well though & fills an important missing link in my headspace.
    It would be cool though if there were a way to 'get under the hood' and inspect the CountRefs of the vba engine

    thx again
    Glenn






     
    0
     
    LVL 2

    Author Comment

    by:valhallatech
    Thanx for all guys - I've allocated lions share to GreymanMSC as creating a close method to explicitly clean up my references - esp cyclic ones is whats fixed the problem. Harfangs $.02+ helped my understanding on the whole issue though and couldn't go without recognition
    thx again
    Glenn
    0

    Write Comment

    Please enter a first name

    Please enter a last name

    We will never share this with anyone.

    Featured Post

    Course: JavaScript Coding - Massive 12-Part Bundle

    Regardless of your programming skill level, you'll go from basics to advanced concepts in a vast array of JavaScript subjects including Sammy.js, Agility.js, Ember.js, Node.js, jQuery, AJAX, Extjs, AngularJS, Knockout.js, and JSON.

    In the previous article, Using a Critera Form to Filter Records (http://www.experts-exchange.com/A_6069.html), the form was basically a data container storing user input, which queries and other database objects could read. The form had to remain op…
    I see at least one EE question a week that pertains to using temporary tables in MS Access.  But surprisingly, I was unable to find a single article devoted solely to this topic. I don’t intend to describe all of the uses of temporary tables in t…
    Familiarize people with the process of utilizing SQL Server functions from within Microsoft Access. Microsoft Access is a very powerful client/server development tool. One of the SQL Server objects that you can interact with from within Microsoft Ac…
    Show developers how to use a criteria form to limit the data that appears on an Access report. It is a common requirement that users can specify the criteria for a report at runtime. The easiest way to accomplish this is using a criteria form that a…

    913 members asked questions and received personalized solutions in the past 7 days.

    Join the community of 500,000 technology professionals and ask your questions.

    Join & Ask a Question

    Need Help in Real-Time?

    Connect with top rated Experts

    17 Experts available now in Live!

    Get 1:1 Help Now