# Monday, 31 December 2007
« Hello World | Main | Argh! Part 2 »
Argh!
using System;

class
Program
{
    WeakReference wr;

    Program()
    {
        wr = new WeakReference(this, true);
        GC.SuppressFinalize(wr);
    }

    ~Program()
    {
        Console.WriteLine(wr.IsAlive);
    }

    static void Main()
    {
        new Program();
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }
}
 

What should the output of this program be? On .NET 1.1 it's True (as expected), but on .NET 2.0 it is False. On .NET 2.0 the GC has special support for finalizing WeakReferences, but it fails to take into account whether GC.SuppressFinalize() has been called (and this may be by design, but it's still broken).

This may look academic, but it is actually a real issue in the implementation of ReferenceQueues on IKVM. Previously I used GCHandle to keep a weak reference back to the reference object that was being monitored, but because that requires full trust, I changed the code to use WeakReference, but that broke it because of the above mentioned behavior. The work around is easy, but ugly and inefficient, simply keep these weak references in a global hashtable to make sure they are always strongly reachable.

.NET really needs a better mechanism for doing post-mortem cleanup (i.e. something like java.lang.ref.ReferenceQueue).

P.S. By my new policy, I won't be filing a bug with Microsoft since they have amply demonstrated not to care about external bug reports.

Monday, 31 December 2007 14:33:49 (W. Europe Standard Time, UTC+01:00)  #    Comments [6]
Monday, 31 December 2007 16:40:21 (W. Europe Standard Time, UTC+01:00)
Are you sure about this? I don't think that what the code is doing is outside the specifications.

The WeakReference finalizer wouldn't get called until after ~Program (otherwise the call to wr.IsAlive wouldn't be valid anyway)... So I don't think that SuppressFinalize() changes what the code does in any way.

In general, suppressing the finalizer on the WeakReference isn't going to stop the object pointed to by the WeakReference being dropped by the GC.Collect() call. As there is no strong reference to the Program class at this point, it gets garbage collected.

So, basically you have created a pathological case where the object you are referencing using a WeakReference is in the process of being finalized. So everything hinges around the definition of what ".IsAlive" means.

As little as I trust MSN when it gets to this level of fiddly-ness, the return value is defined as:
"true if the object referenced by the current WeakReference object has not been garbage collected and is still accessible; otherwise, false"

Basically in .NET 1.1 an object "being finalized" is alive, in .NET 2.0 it isn't. In this case the object hasn't yet been garbage collected, but crucially it must be inaccessible as it is half way through the gc process.


To be honest, I may have misunderstood the purpose of a "long weak reference" as I have never used them (like a lot of features, they don't sound particularly useful outside trying to exploit security problems), so I may be missing something important.

My reading of the docs implies that Target will not return NULL while IsAlive returns false... and you get some form of access to a semi-initialised class that thinks that it has been garbage collected, but hasn't, and you can try to call methods on it, to see what bits go boom :)

DDD
DDD
Monday, 31 December 2007 18:37:26 (W. Europe Standard Time, UTC+01:00)
DDD is wrong - there is no ordering dependency for finalizers, that's one of the things that makes them hard to write. In the code that Jeroen posted, the code could validly throw a null reference exception.

That WeakReference in the past had a finalizer, IMHO, was an implementation detail and shouldn't have been fiddled with by deregistering this finalizer manually. I find myself wondering if this deregistering behaviour wasn't a resource leak.

Did deregistering finalization on a WeakReference, in .NET 1.1, cause resource leaks? (I'm wondering if it leaked GCHandles internally.)
Monday, 31 December 2007 20:00:55 (W. Europe Standard Time, UTC+01:00)
Whoops - you're correct :) There can be no defined ordering of finalizers in managed code - in any case, we could conceivably end up with a loop of finalizers calling each other's methods.

I still think that .IsAlive returning false is the correct thing to do in this case though... The Program object has been marked for garbage collection. For all intents and purposes, it's gone.

What is the reason to believe that it is the WeakReference finalizer that is responsible for changing the return value of IsAlive to false in this case? In the example, IsAlive should return false whether or not the WeakReference object itself is going to be garbage collected...

DDD
DDD
Monday, 31 December 2007 20:55:47 (W. Europe Standard Time, UTC+01:00)
Back to real.
I agree with DDD that the value of IsAlive is false not because the WeakReference finalizer has been called , but because the object is going to be garbage collected, so it is not valid any more, if this was not the way in 1.1 then it is broken there.
Monday, 31 December 2007 21:10:48 (W. Europe Standard Time, UTC+01:00)
I inherited from WeakReference to track the finalizer and it's not called when SuppressFinalize is used.
Wednesday, 02 January 2008 04:44:44 (W. Europe Standard Time, UTC+01:00)
>P.S. By my new policy, I won't be filing a bug with Microsoft since they have amply demonstrated not to care about external bug reports.

It's really unfortunate that MS is this bad with feedback. Especially from someone with such deep understanding and contribution to the community...
Kevin
Name
E-mail
Home page

I apologize for the lameness of this, but the comment spam was driving me nuts. In order to be able to post a comment, you need to answer a simple question. Hopefully this question is easy enough not to annoy serious commenters, but hard enough to keep the spammers away.

Anti-Spam Question: What method on java.lang.System returns an object's original hashcode (i.e. the one that would be returned by java.lang.Object.hashCode() if it wasn't overridden)? (case is significant)

Answer:  
Comment (HTML not allowed)  

Live Comment Preview