.NET Forum / .NET Framework / CLR / November 2003
Object state after finalization (was InvalidOperationException from invalid GCHandle in WeakReference)
|
|
Thread rating:  |
Niall - 10 Nov 2003 23:52 GMT This is related to my previous post about getting an InvalidOperationException from a WeakReference. Since my original post, another programmer has created a situation where this is happening. However, it is happening at shutdown where all the finalizers are being run. I do not think this person's code is correct, I think it's doing things in a Finalizer that it shouldn't be.
I know that at shutdown time, objects will be finalized (in general, at least.. it seems as though you can't rely on them being finalized). And I know that finalization can occur in any order, which is why you can't rely on an object's references when you are running that object's finalizer. What I think is happening in this case is that this object's WeakReference is being finalized before the object itself is. When the object's finalizer does its thing, the WeakReference gives the exception. However, there isn't a NullReferenceException, it's an exception from the internals of the WeakReference.
So this makes it appear to me that the WeakReference object is still alive. It has been finalized, and is now waiting in the heap for the next run of the GC, whereupon it will be identified as garbage and removed. I know that at shutdown, there won't be another GC, but I think this may be the same situation as I found myself in previously, which was during runtime, before shutdown.
So am I right in assuming that if we catch the WeakReference after finalization but before the GC removes it, it will be in this state? I guess the only way you could access a WeakReference in this state (short of resurrection situations) would be from another object's finalizer. I say this because if it was from normal code in another object, then there would be a root path to the WeakReference, and it wouldn't have been finalized in the first place.
I think the problematic code in this case needs a refactor such that whatever work it is doing is done either in a Dispose method or in something else deterministic and under our control, rather than using a finalizer to catch when an object is no longer used because we haven't designed well enough to know that from our own code.
Niall
Niall - 11 Nov 2003 01:14 GMT Further to this question, I have the following request for clarification, which I think is best asked through an example.
Let's say we have three objects - A, B and C. A refers to B, and B refers to C (ie A -> B -> C). A and C have finalizers, but B does not. Let's say that nothing is referencing A, and the GC kicks in.
All three objects are garbage, and A and C need to be finalized. So A and C get shifted off to the freachable queue, and there are now roots to both objects. So my first question is: seeing as A referenced B, and there is now a root to A, will B now be upgraded from garbage to living? Or will it be collected?
If B remains alive, waiting for A's final collection, what happens if A's finalizer calls some cleanup method (perhaps Dispose) on B? B has no way of knowing that C might have already been finalized. So there is the possibility that this cleanup method might have problems. From a design point of view, what's the correct behaviour here? Should finalizers not call cleanup methods on other objects? Or should cleanup methods deal with the possibility that the objects they use may have been finalized?
I hope my post is clear enough for other people to understand. Thinking about this stuff is twisting knots inside my head.
Niall
Jon Skeet [C# MVP] - 11 Nov 2003 08:39 GMT > Further to this question, I have the following request for clarification, > which I think is best asked through an example. [quoted text clipped - 8 lines] > a root to A, will B now be upgraded from garbage to living? Or will it be > collected? I don't believe B is truly marked as garbage in the first place. It will be alive until A and C are marked as garbage rather than just freachable.
> If B remains alive, waiting for A's final collection, what happens if A's > finalizer calls some cleanup method (perhaps Dispose) on B? B has no way of [quoted text clipped - 3 lines] > cleanup methods on other objects? Or should cleanup methods deal with the > possibility that the objects they use may have been finalized? It should always be safe to call Dispose on an object which implements IDisposable, even if it's already been disposed - your implementation of Dispose should make sure of that.
 Signature Jon Skeet - <skeet@pobox.com> http://www.pobox.com/~skeet If replying to the group, please do not mail me too
Niall - 13 Nov 2003 02:22 GMT inline
> I don't believe B is truly marked as garbage in the first place. It > will be alive until A and C are marked as garbage rather than just > freachable. Hmm, you could be right. I was basing my understanding from those two consecutive MSDN articles on the GC, where it says:
"You'll notice in the previous section that when an application is no longer accessing a live object, the garbage collector considers the object to be dead. However, if the object requires finalization, the object is considered live again until it is actually finalized, and then it is permanently dead. In other words, an object requiring finalization dies, lives, and then dies again. This is a very interesting phenomenon called resurrection. Resurrection, as its name implies, allows an object to come back from the dead." - http://msdn.microsoft.com/msdnmag/issues/1100/gci/
However, it could well have been a simplification, and also that article is quite old now (I think it is from the end of 2001). It would make sense to me if the GC did work as you say.
> > If B remains alive, waiting for A's final collection, what happens if A's > > finalizer calls some cleanup method (perhaps Dispose) on B? B has no way of [quoted text clipped - 7 lines] > IDisposable, even if it's already been disposed - your implementation > of Dispose should make sure of that. Sorry, I wasn't referring to re-disposing a control that's already been disposed. I was referring to, when finalizing, calling Dispose() on a Control, which will call its Dispose(bool disposing) method with disposing = true. I was wondering if it's bad to call Dispose in such a way where the Control thinks it's not being finalized, but where, in reality, everything of the form it was on is being finalized around it.
Basically what I mean is, is either one of the following bad code for some reason:
~SomeClass() { SomeOtherControl.Dispose(); base.Finalize(); }
or, in SomeClass:
protected override Dispose(bool disposing) { if (disposing) { ... } SomeOtherControl.Dispose(); ... base.Dispose(disposing); }
Hope this makes some sense,
Niall
Jon Skeet [C# MVP] - 13 Nov 2003 07:23 GMT > Basically what I mean is, is either one of the following bad code for some > reason: [quoted text clipped - 19 lines] > > Hope this makes some sense, I don't think either is particularly bad, no. Of course, if you don't call dispose there then the finalizer will get it next time the GC collects that generation anyway - and you're already in the case where someone has failed to call Dispose properly if this is coming up.
 Signature Jon Skeet - <skeet@pobox.com> http://www.pobox.com/~skeet If replying to the group, please do not mail me too
Sebastien Lambla - 11 Nov 2003 15:20 GMT You should implement IDisposable on both A, B and C. In the Dispose(bool) prototype, just make sure you call the Dispose on all direct child objects, and that you do call GC.SuppressFinalize().
But, frankly, there are very few cases where it's a good idea to use the Finalizer for anything. Especially if you're using a WeakReference. Unless you hold OS resources!
I'm afraid I don't have enough background on the whole story before knowing what to say
 Signature Sebastien Lambla http://thetechnologist.is-a-geek.com/blog/
> Further to this question, I have the following request for clarification, > which I think is best asked through an example. [quoted text clipped - 21 lines] > > Niall Niall - 13 Nov 2003 04:37 GMT In this particular case, the object with the finalizer is hooking itself up to some internal classes that manage local machine data concurrency issues (ie two separate forms in the application both open, one affecting the other's data). It's supposed to be transparent from the programmers here, so that it doesn't need any extra hands on work. So the reason for not doing it with a dispose is that there is no apparent reason for anyone using the class to suspect it would need disposing, and I think in a lot of cases, people would forget to call the dispose, or be confused as to why it's there.
The other trouble is that it seems the exact lifetime of this object is not controlled by any one other object - ie: No one place in the code in the system could definitively say "Ok, this object is now finished with" and call dispose. This is because the object can potentially be shared between different forms, etc. Personally, I think a situation where nobody knows when an object has finished its purpose is indicative of a hole in the design somewhere. But I haven't been able to come up with a better idea to propose to the person who wrote it, and they're not exactly open minded about the potential for design flaws in *their* code :P
Niall
> You should implement IDisposable on both A, B and C. In the Dispose(bool) > prototype, just make sure you call the Dispose on all direct child objects, [quoted text clipped - 38 lines] > > > > Niall Jon Skeet [C# MVP] - 13 Nov 2003 07:25 GMT > The other trouble is that it seems the exact lifetime of this object is not > controlled by any one other object - ie: No one place in the code in the [quoted text clipped - 5 lines] > propose to the person who wrote it, and they're not exactly open minded > about the potential for design flaws in *their* code :P My feeling is the same as yours. Personally I'd work out a sort of reference counting system for this - have a proxy object which controls the lifetime, and each owner has to declare its ownership and then release it, and if the count then goes down to zero, the proxy calls Dispose.
That has all the normal inherent problems of reference counting, of course - in particular if two of these objects can "own" each other - but for many cases it should work fine.
 Signature Jon Skeet - <skeet@pobox.com> http://www.pobox.com/~skeet If replying to the group, please do not mail me too
Sebastien Lambla - 13 Nov 2003 11:15 GMT Proxying and ref counting is doable under .net, but it has it's share of problems as you mentionned.
As to knowing when an object is or is not garbage collected, holding a short WeakRef on an object, and not disposing the WeakRef whatever happens is the best way to track that. I repeat it, but using the IDisposable pattern is the best way to handle finalization, finalizer in c# should only rely on your dispose method if you do so
 Signature Sebastien Lambla http://thetechnologist.is-a-geek.com/blog/
> > The other trouble is that it seems the exact lifetime of this object is not > > controlled by any one other object - ie: No one place in the code in the [quoted text clipped - 15 lines] > course - in particular if two of these objects can "own" each other - > but for many cases it should work fine.
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|