.NET Forum / .NET Framework / CLR / October 2004
Weak Reference Race Condition
|
|
Thread rating:  |
Chris Mullins - 15 Jul 2004 23:55 GMT If I have an object held only by a weak reference, and I write code that looks like:
dim strongRef as Object if wr.IsAlive() then strongRef=wr.Target else strongRef=new MyObject() end if
There is a fairly signifigant race condition - between the "wr.IsAlive" call and the wr.Target call, the object could be garbage collected. To the best that I can find, there is no mechanism by which I can lock the Weak Reference to prevent a GC between my IsAlive check, and the rooting of the object.
To make this code legit, and work in all cases it needs to look like:
dim strongRef as Object if wr.IsAlive() then strongRef=wr.Target if strongRef is nothing then strongRef = new MyObject() else strongRef=new MyObject() end if
It seems like there should be a way to avoid that extra check for the "IsAlive" case.
What am I missing?
 Signature Chris Mullins
mikeb - 16 Jul 2004 00:26 GMT > If I have an object held only by a weak reference, and I write code that > looks like: [quoted text clipped - 26 lines] > > What am I missing? Why not just skip the IsAlive check?
dim strongRef as Object = Nothing
try strongRef=wr.Target catch e as InvalidOperationException ' do nothing end try
if strongRef is nothing then strongRef = new MyObject() end if
If the target object type has a finalizer, you should catch the exception - it's possible for it to be thrown if the finalizer has been run, but the object has not yet been collected (at least that's my reading of the docs).
 Signature mikeb
Chris Mullins - 16 Jul 2004 00:37 GMT "mikeb" <mailbox.google@nospam.mailnull.com> wrote :
> > There is a fairly signifigant race condition - between the "wr.IsAlive" call > > and the wr.Target call, the object could be garbage collected. To the best [quoted text clipped - 15 lines] > strongRef = new MyObject() > end if I suspect this would work - it just seems.... inelegant. Which is usually a sign that there's a better way.
I keep looking for somthing akin to a SyncLock that would block the GC from doing a collect while the lock is heald. I *know* there's nothing like this, but I keep looking anway...
 Signature Chris Mullins
Dmitriy Zaslavskiy - 16 Jul 2004 03:56 GMT dim strongRef as Object = wr.Target If strongRef is Nothing Then ' It's alive and now will not be collect since you have strongref Endif
That's all you need. No try/catch is needed.
> "mikeb" <mailbox.google@nospam.mailnull.com> wrote : >> > There is a fairly signifigant race condition - between the "wr.IsAlive" [quoted text clipped - 29 lines] > this, > but I keep looking anway... Niall - 28 Jul 2004 07:13 GMT > dim strongRef as Object = Nothing > [quoted text clipped - 8 lines] > run, but the object has not yet been collected (at least that's my > reading of the docs). The InvalidOperationException is thrown when the WeakReference has been finalized and you access the Target or IsAlive properties, not the target itself.
Niall
Dmitriy Zaslavskiy - 02 Aug 2004 02:38 GMT Can you provide a sample. Because there must something else wrong with your code. I cannot cause Target to throw an InvalidOperationException
>> dim strongRef as Object = Nothing >> [quoted text clipped - 14 lines] > > Niall mikeb - 02 Aug 2004 18:36 GMT > Can you provide a sample. > Because there must something else wrong with your code. > I cannot cause Target to throw an InvalidOperationException On browsing the code for WeakReference in Reflector, it looks like the InvalidOperationException can be thrown when there's some problem with the GC allocating an internal handle for the weak reference (GCHandle.InternalAlloc() returns 0). But, the exception would not get thrown until you tried to access the Target or IsAlive properties.
So there is code that throws the exception in those properties. However, it's unclear to me exactly what conditions will cause the exception to be thrown.
>>> dim strongRef as Object = Nothing >>> [quoted text clipped - 14 lines] >> >>Niall
 Signature mikeb
Niall - 04 Aug 2004 08:23 GMT Dmitriy, Mike
If you read the code for WeakReference.Finalize, you will see it sets the handle to 0 and frees the GCHandle object it wraps. As I said in my last post, you will get an InvalidOperationException if you try to access the Target or IsAlive once the WeakReference has been finalized. This is because upon finalization, the WR has cleaned up its GCHandle, so the WR can no longer tell you anything about what the Target is/was and whether it is alive or not.
Niall
> > Can you provide a sample. > > Because there must something else wrong with your code. [quoted text clipped - 28 lines] > >> > >>Niall mikeb - 04 Aug 2004 17:35 GMT > Dmitriy, Mike > [quoted text clipped - 5 lines] > longer tell you anything about what the Target is/was and whether it is > alive or not. If that's the only way that the GCHandle can become 0, then it seems to me that you'd only get the exception if you're accessing the WeakReference via another WeakReference (otherwise it would not be able to be finalized). Or am I missing something?
That possibility had not crossed my mind - the idea of using WeakReferences to WeakReferences makes my head hurt.
> Niall > [quoted text clipped - 33 lines] >>>> >>>>Niall
 Signature mikeb
Niall - 05 Aug 2004 03:42 GMT It can happen if you're using WRs from classes with Finalizers. Imagine an object of Class A has a Finalizer, and also has the only reference to a WR x. Something else has a WR to object A. A becomes unreachable, so x becomes unreachable. The order of finalization isn't guaranteed, so it is possible to have the WR finalized before A is finalized. Then you can have all kinds of race conditions, etc, in order to get at the finalized WR.
Granted, it's bizarre. I didn't write the stupid code in our system that had this problem, and there's not enough painkillers in the world to get me through fixing it (apparently deleting it isn't an option), so I learned about when the InvalidOperationException could happen and dealt with it :P
The m_handle is internal in the class. So it is possible that it could be set externally, somewhere in mscorlib. However, it could just be for reading purposes, who knows.
Niall
> > Dmitriy, Mike > > [quoted text clipped - 51 lines] > >>>> > >>>>Niall mikeb - 05 Aug 2004 17:59 GMT > It can happen if you're using WRs from classes with Finalizers. Imagine an > object of Class A has a Finalizer, and also has the only reference to a WR [quoted text clipped - 4 lines] > > Granted, it's bizarre. I figured that the conditions for the exception would be bizarre - I've never seen the exception myself, but I try to code for it since the docs claim it can happen. Apparently you have direct experience with it.
As an aside - I wonder why they throw an exception instead of simply returning null, since the net effect is that you can't a reference to the target. In fact, I use a static method to access the target that simply catches the exception and returns null in that case (not that that code path has ever been hit as far as I know).
> I didn't write the stupid code in our system that had > this problem, and there's not enough painkillers in the world to get me [quoted text clipped - 74 lines] >>>>>> >>>>>>Niall
 Signature mikeb
Niall - 06 Aug 2004 02:12 GMT > I figured that the conditions for the exception would be bizarre - I've > never seen the exception myself, but I try to code for it since the docs > claim it can happen. Apparently you have direct experience with it. I wish I hadn't :P
> As an aside - I wonder why they throw an exception instead of simply > returning null, since the net effect is that you can't a reference to > the target. In fact, I use a static method to access the target that > simply catches the exception and returns null in that case (not that > that code path has ever been hit as far as I know). I think the difference is that returning null for the target may lead to the wrong impression. If you return null, you can't tell if the WR ever had a real target in the first place. Also, there's no correct return value for the IsAlive property because the WR has been disconnected from its target, so the target may still be alive, or it may be dead as well. So I think from a correctness point of view, the exception is the right way to go because the WR no longer can tell anything about the target it once had. Obviously, the net effect of the exception for most cases is that the WR is now useless and you can't get to your target any more, so sooner or later you'll end up with a null.
Niall
Dmitriy Zaslavskiy - 06 Aug 2004 14:06 GMT Well, In whidbey your wishes came true and it just returns null instead of throwing exception
>> I figured that the conditions for the exception would be bizarre - I've >> never seen the exception myself, but I try to code for it since the docs [quoted text clipped - 25 lines] > > Niall mikeb - 06 Aug 2004 17:50 GMT >>I figured that the conditions for the exception would be bizarre - I've >>never seen the exception myself, but I try to code for it since the docs [quoted text clipped - 13 lines] > the IsAlive property because the WR has been disconnected from its target, > so the target may still be alive, or it may be dead as well. I hadn't thought of the situation where the WR might be gone (or finalizing) while the target is still around...
My scenarios for using WRs have been simple - a poor man's cache usually - so I don't think I would have run into any of these complexities. But the education is well worthwhile.
> So I think from > a correctness point of view, the exception is the right way to go because [quoted text clipped - 4 lines] > > Niall
 Signature mikeb
Niall - 09 Aug 2004 01:35 GMT > I hadn't thought of the situation where the WR might be gone (or > finalizing) while the target is still around... > > My scenarios for using WRs have been simple - a poor man's cache usually > - so I don't think I would have run into any of these complexities. But > the education is well worthwhile. They can be very handy for lots of things. Apart from caching, we also use them for testing our application for memory leaks - take a WR to an object, do something, get to a point when the object should be dead (you may need to nudge the GC) and check the WR to see if all has gone as expected. This can be useful to provide a general level of protection against shipping something that has a leak in the architecture, but it's obviously not going to catch every possible leak. Sometimes we run into issues where the GC is not behaving as we expected, but at the end of the day, the GC will do what it wants and we just have to live with it :P
Niall
Sankar Nemani - 13 Sep 2004 23:05 GMT Does this exception not have anything to do with whether the weakreference has been created as a longweakreference (i.e., track after finalization set to true) or a shortweakreference?
> > I hadn't thought of the situation where the WR might be gone (or > > finalizing) while the target is still around... [quoted text clipped - 14 lines] > > Niall Niall - 22 Oct 2004 06:38 GMT Sorry, only just saw this post now.
I doubt it has anything to do with long or short references. We have no use for long references at the moment, so all of ours are short. It's been a long time since I saw the InvalidOperationException, but I remember being completely baffled as to how the code came to that state at the time, and I don't think I'm much the wiser now :P
Niall
> Does this exception not have anything to do with whether the weakreference > has been created as a longweakreference (i.e., track after finalization set [quoted text clipped - 23 lines] > > > > Niall Dmitriy Zaslavskiy - 06 Aug 2004 14:09 GMT Sorry didn't follow the thread. But I checked it you are 100% correct.
> It can happen if you're using WRs from classes with Finalizers. Imagine an > object of Class A has a Finalizer, and also has the only reference to a WR [quoted text clipped - 79 lines] >> >>>> >> >>>>Niall
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 ...
|
|
|