Click to See Complete Forum and Search --> : Living in the GC world.


Bill McCarthy
10-08-2000, 07:01 AM
Hi all,

Okay, so it looks like VB.NET has to live in the GC memory management
system. Adding ref counting *on top* of the GC doesn't seem like an easy or
robust thing to do. I'm not saying it shouldn't be done, just that it seems
highly unlikely that MS will.

So at present we seem to be faced with accpeting the IDispose interface
(assuming that is implemented), but that still doesn't address the issue of
shared objects. Anyway to cut a long (or should that be int) story short, I
suggested one thing that might be able to help, that would at least enable
us to maintain existing object models is some added functionality to the
IDispose interface.

First, if Dispose was to be made so that re-entry does not occur, it would
be reasonably possible to have casscading tear down without cycling causing
too many problems.

Secondly, if there was a way to call a specific partial or smart GC trace
for the object that get's it's dispose called. Eg:

Sub Dispose(...)
If GC.IsObjShared(Me) Then

The smart trace should probably not trace branches stemming from the object
being traced, although circular references could still pose a problem, but
that's nothing new to VB <g>.
<sidenote> It appears that although the GC supposedly addresses some
circular reference issues, it certainly does not address those arising out
of tear down casscading </sidenote>


So, does anyone think that will work ? any problems with it ??

Jeff Peil
10-08-2000, 08:40 AM
"Bill McCarthy" <Bill_McC@iprimus.com.au> wrote in message
news:39e051f9@news.devx.com...
> Hi all,
>
> Okay, so it looks like VB.NET has to live in the GC memory management
> system. Adding ref counting *on top* of the GC doesn't seem like an easy
or
> robust thing to do. I'm not saying it shouldn't be done, just that it
seems
> highly unlikely that MS will.

I'll agree that it isn't going to happen, though we don't agree on the
reasons.

> First, if Dispose was to be made so that re-entry does not occur, it would
> be reasonably possible to have casscading tear down without cycling
causing
> too many problems.

You can do this yourself pretty cheaply.

> Secondly, if there was a way to call a specific partial or smart GC trace
> for the object that get's it's dispose called. Eg:
>
> Sub Dispose(...)
> If GC.IsObjShared(Me) Then
>
> The smart trace should probably not trace branches stemming from the
object
> being traced, although circular references could still pose a problem, but
> that's nothing new to VB <g>.
> <sidenote> It appears that although the GC supposedly addresses some
> circular reference issues, it certainly does not address those arising out
> of tear down casscading </sidenote>
>
>
> So, does anyone think that will work ? any problems with it ??

This would be pretty problematic in a multithreaded world (meaning the .NET
environment.) Based on the GC's need to suspend all threads in managed
space while it runs (not all GCs have this requirement, but GCs that run
concurrently aren't easy on performance,) it would need to do the same for
your trace (pricey.) Further what happens if just after the trace has
returned true, another thread makes another reference to the object, your
dispose method would continue merrily along. This of course also is
conveniently ignoring exactly how you define shared (I'm presuming you mean
more then one reference, that for the purposes of GC, is still valid, but
that might exclude more references then you would expect.) I also have to
wonder how valuable this is, compared to invoking the GC explicitly from the
context where you would have been calling dispose (the full GC process is
probably worth running, if you're going to bring about this overhead
anyways.)

Bill McCarthy
10-08-2000, 10:02 AM
Hi Jeff,

"Jeff Peil" <jpeil@bigfoot.com> wrote in message
news:39e068d2$1@news.devx.com...
>
> > First, if Dispose was to be made so that re-entry does not occur, it
would
> > be reasonably possible to have casscading tear down without cycling
> causing
> > too many problems.
>
> You can do this yourself pretty cheaply.
>

Yeh, I know, I even said that in the dotnet list, but it is something people
often overlook. It means you need another Try.. Finally block in your
dispose method to ensure the flaggets reset, that's why I propose it as
being integrated into the IDispose interface.

>
> This would be pretty problematic in a multithreaded world (meaning the
..NET
> environment.) Based on the GC's need to suspend all threads in managed
> space while it runs (not all GCs have this requirement, but GCs that run
> concurrently aren't easy on performance,) it would need to do the same for
> your trace (pricey.)

The suspending of other threads while terminating a large shared object is
probably worth it. Note: you don't have to call on the partial trace
function on all objects, only those that you think might be shared.

> Further what happens if just after the trace has
> returned true, another thread makes another reference to the object, your
> dispose method would continue merrily along.

The same kind of problems could happen in any ref counted system. The issue
you raise is to do with thread synchronisation when thay are using the same
reference. When talking about migrating VB apps and existing object models,
I don't think that issue is really relevant, do you ?

> This of course also is
> conveniently ignoring exactly how you define shared (I'm presuming you
mean
> more then one reference, that for the purposes of GC, is still valid, but
> that might exclude more references then you would expect.)

Is that meant to be cryptic or something ??

> I also have to
> wonder how valuable this is, compared to invoking the GC explicitly from
the
> context where you would have been calling dispose (the full GC process is
> probably worth running, if you're going to bring about this overhead
> anyways.)
>

The idea is to be able to have ordered tear down, IF needed. Calling
GC.Collect ( I hope that doesn't run finaliser) is just going to mark and or
compact. If it runs finalisers then that's the problem we are trying to
avoid as finalisers can be called on any of the child objects before they
are called on the parent object and as such present no way what-so ever to
get ordered tear down.
Also, I expect a simple trace search to be much more efficient than a
collect. There will be times where the overheads are high and othertimes
where it is quite low and that would be dependant on the object model.

My idea on this, is not that it's a cure all, but more considering what is
on offer, it's something that will make it possible at least to safely be
able to have a tear down order, admittely still reliant on the caller to
call dispose, but it will help in the migration from VB.

AFAIC, the only other option is to have ref counting, which would be nice,
as long as the user didn't have to write addref/releaseref all the time.
Unfortunately it seemed to me that not only wasn't that on offer from MS,
but had been also discarded by them. And I'm definetly not happy about. So
i'm just exploring alternatives.

Bill McCarthy
10-08-2000, 10:23 AM
"Bill McCarthy" <Bill_McC@iprimus.com.au> wrote in message
news:39e07c3d@news.devx.com...
>
> The idea is to be able to have ordered tear down, IF needed. Calling
> GC.Collect ( I hope that doesn't run finaliser) is just going to mark and
or
> compact. If it runs finalisers then that's the problem we are trying to
> avoid as finalisers can be called on any of the child objects before they
> are called on the parent object and as such present no way what-so ever to
> get ordered tear down.


Actually, I s'pose you could enforce circular references, then in each child
object when it's finaliser get's called, check to see if it m_parent is
nothing if not interupt the finaliser thread, divert it back to the parent
object etc. That would have headaches though, as that means when an object
get's init it's parent must be set, and when it gets disposed of it's
m_parent must be set to nothing as you would need to distinguish between the
parent being released and just some child object being released.. hmm.. a
possibility, but it's ugly and prone to more errors than cycles as you need
to introduce circular references in the first place to get it to work

David Bayley
10-08-2000, 02:19 PM
Bill,

> Actually, I s'pose you could enforce circular references, then in each
child
> object when it's finaliser get's called, check to see if it m_parent is
> nothing if not interupt the finaliser thread, divert it back to the parent
> object etc. That would have headaches though, as that means when an
object
> get's init it's parent must be set, and when it gets disposed of it's
> m_parent must be set to nothing as you would need to distinguish between
the
> parent being released and just some child object being released.. hmm.. a
> possibility, but it's ugly and prone to more errors than cycles as you
need
> to introduce circular references in the first place to get it to work

That sounds pretty good to me. I came to the same conclusion because I
couldn't follow the line of reasoning in this thread, but then realised your
saying the same thing. I don't quite understand the headaches you've
pointed out though.

Setting an m_Parent reference in the child is usually preferable anyway to
let the child know what it's parent is and that it isn't in a "parentless"
state. Also, often the parent acts as a factory class for the children
anyway, so a parentless child can never exist, and in these situations you
simply remove the child finalizer and rely on the parent's finalizer to call
children.dispose.

In cases where the children can be parentless, it may be a little more ugly,
but at least it's encapsulated in the object model. Circular references
aren't a problem under the GC. I like it... it nails the ordered tear-down
AFAICT.


A shared resource could be wrapped with objects designed not to be shared.
The wrappers would call AddRef in the constructor, and call Release in
Dispose. That way the clients aren't responsible for calling
AddRef/Release, just the usual Dispose. The downside is that the client
must be careful not to pass references to the wrapper objects around, rather
must create a new wrapper object. I'm finding it difficult to think of good
examples of shared resources though, so might not have thought this through
properly.


--
David.

David Bayley
10-09-2000, 09:33 AM
Bill,

Saw your messages on DOTNET-L, and had to write some pseudo-code to think it
through properly...

Class MyParent

Private m_Children As Collection 'of MyChild

Sub Dispose
Finalize()
GC.SuppressFinalize(Me)
End Sub

Sub Finalize
m_Children.Dispose 'for each MyChild
'clean up Me
End Sub

End Class

Class MyChild

Private m_Parent As MyParent 'might be Nothing

Sub Dispose
m_Parent = Nothing
Finalize()
GC.SuppressFinalize(Me)
End Sub

Sub Finalize
If m_Parent Is Nothing Then
'clean up Me
Else
'this is the crux of it, which according to the docs is
'"useful inside a finalizer that needs to resurrect itself"
GC.ReRegisterForFinalize(Me)
'wait for MyParent.Finalize
End If
End Sub

End Class

If, when MyChild.Finalize is just removed, we can't guarantee that MyChild
is still around when MyParent.Finalize is called, then this might work for
that too.

Probably should wait for more info from MS on this, but wadd'ya think?

Even if it does work, my concern is that it appears to go against the
recommendations of doing any lengthy operations in Finalize, in this case
calling all the m_Children.Dispose in MyParent.Dispose.

--
David.

Bill McCarthy
10-09-2000, 09:47 PM
Hi David,

"David Bayley" <dbayley@aebacus.com> wrote in message
news:39e1c762@news.devx.com...
>
> If, when MyChild.Finalize is just removed, we can't guarantee that MyChild
> is still around when MyParent.Finalize is called, then this might work for
> that too.
>
> Probably should wait for more info from MS on this, but wadd'ya think?
>
> Even if it does work, my concern is that it appears to go against the
> recommendations of doing any lengthy operations in Finalize, in this case
> calling all the m_Children.Dispose in MyParent.Dispose.
>
> --

I think my concerns are much the same as yours. Although that may work,
it's not what the finalise is meant to be used for, as in if anything goes
wrong in that, it will kill all other pending finalisers. It's also limited
to very simple models. Say for instance you have a node, which can store
nodes and each node can store pages and or nodes. So you have a casscading
set of parent/child objects, where a child can be a parent. If you force
the child finaliser up to the parent, you have no guarantee that the parent
has been released or should be finalised, so you need some extra logic of
some sort in there ...

I've seen on dotnet claims that child objects want get destroyed before any
parent if only the parent has a finaliser, (although this still doesn't
address the issue of a node containing a node type of thing.). I am still
dubious on that. I really want to see it spelt out exactly what the GC
does, it's sequences and threading etc. I was under the impression that all
objects due to be collected/finalised might not actually be found in any one
sweep, but it depends on exactly how they have implemented their GC.

But I do think there is a sol'n here, somewhere, that will enable to get an
ordered destruction of simple parent/child models. (possibly). But we
really need some clarrification on how things are done to be sure.

Of course, it still doesn't address the issue of DF, without having to have
the huge overhead of calling a collect or waitforfinalisers.

David Bayley
10-10-2000, 06:32 AM
Bill,

> I think my concerns are much the same as yours. Although that may work,
> it's not what the finalise is meant to be used for, as in if anything goes
> wrong in that, it will kill all other pending finalisers.

Yeah, it's getting colder.

> It's also limited
> to very simple models. Say for instance you have a node, which can store
> nodes and each node can store pages and or nodes. So you have a
casscading
> set of parent/child objects, where a child can be a parent. If you force
> the child finaliser up to the parent, you have no guarantee that the
parent
> has been released or should be finalised, so you need some extra logic of
> some sort in there ...

I did think about that, and it looks like it will cascade to children's
children. The child only needs to call Dispose for each grandchild in the
clean
up section of the finalizer. It does however exaggerate the concerns we
have.

> I've seen on dotnet claims that child objects want get destroyed before
any
> parent if only the parent has a finaliser, (although this still doesn't
> address the issue of a node containing a node type of thing.). I am still
> dubious on that. I really want to see it spelt out exactly what the GC
> does, it's sequences and threading etc. I was under the impression that
all
> objects due to be collected/finalised might not actually be found in any
one
> sweep, but it depends on exactly how they have implemented their GC.

Just read Jeff's description of the GC which helped, and does seem to imply
that child objects without a finalise can be guaranteed to be around.

> But I do think there is a sol'n here, somewhere, that will enable to get
an
> ordered destruction of simple parent/child models. (possibly). But we
> really need some clarrification on how things are done to be sure.

I think it will be dependant on the particular resource clean up needed.
Something like the XML tree that is modelled and needs to be saved to disk,
will have to be cancelled if the Dispose call is not made explicitly. In
other models where the clean up is safe and fast, the finalize can be used
to tear-down the tree. That just leaves the grey area in between ;-)

--
David.

Bill McCarthy
10-10-2000, 06:42 AM
"David Bayley" <dbayley@aebacus.com> wrote in message
news:39e2ee8a@news.devx.com...
<snip>
> That just leaves the grey area in between ;-)
>


LOL! Well I must be having one of those days when there's no black or white
<g>