Want to win a PS4? Go Premium and enter to win our High-Tech Treats giveaway. Enter to Win

x
?
Solved

Class_Terminate not firing

Posted on 2004-10-26
10
Medium Priority
?
1,733 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
Comment
Question by:valhallatech
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 4
  • 3
  • 3
10 Comments
 
LVL 58

Assisted Solution

by:harfang
harfang earned 600 total points
ID: 12418830
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
ID: 12418855
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:
GreymanMSC earned 1400 total points
ID: 12418957
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
VIDEO: THE CONCERTO CLOUD FOR HEALTHCARE

Modern healthcare requires a modern cloud. View this brief video to understand how the Concerto Cloud for Healthcare can help your organization.

 
LVL 16

Expert Comment

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

:)
0
 
LVL 58

Expert Comment

by:harfang
ID: 12419097
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
ID: 12419169
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
ID: 12419171
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
ID: 12419452
> 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
ID: 12419884
>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
ID: 12429420
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

Featured Post

What does it mean to be "Always On"?

Is your cloud always on? With an Always On cloud you won't have to worry about downtime for maintenance or software application code updates, ensuring that your bottom line isn't affected.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

In earlier versions of Windows (XP and before), you could drag a database to the taskbar, where it would appear as a taskbar icon to open that database.  This article shows how to recreate this functionality in Windows 7 through 10.
Access developers frequently have requirements to interact with Excel (import from or output to) in their applications.  You might be able to accomplish this with the TransferSpreadsheet and OutputTo methods, but in this series of articles I will di…
With Microsoft Access, learn how to start a database in different ways and produce different start-up actions allowing you to use a single database to perform multiple tasks. Specify a start-up form through options: Specify an Autoexec macro: Us…
With Secure Portal Encryption, the recipient is sent a link to their email address directing them to the email laundry delivery page. From there, the recipient will be required to enter a user name and password to enter the page. Once the recipient …

636 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