Servergeek
Mickey Williams' weblog


Powered by Blogger Pro™

Friday, April 25, 2003

More on the MSDN GC Article


Thinking about this a bit more...

Perhaps if I show a more elaborate example. Consider classes X and Y, where X implements IDisposable and Y does not:

class X: IDisposable {
    Y _y = new _Y();
    public Y Y {
        get { return _y; }
        set { _y = value;}
    }
    ...
}
class Y {
    Z _z;
    public Z Z {
        get { return _z; }
        set { _z = value;}
    }
}

Instances of X (and Y) are created:

X _x = new X();

Time passes, and in recognition of long and dedicated service, _x and _y move into generation 1 in the heap.

The _x and _y instances are dreaming of promotion to generation 2, when _y aquires a reference to _z, an instance of class Z:

_z.Y.Z = new Z();

Shortly after _y aquires a reference to _z, _x finds itself without portfolio, and becomes untraceable. At this point, I believe the following things are true:

  • _z will survive a generation 0 collection.
  • this property is not due to _x implementing a finalizer.
  • I'm assuming that _y's reference to _z will be considered a root until a collection occurs that includes _y.

Since object _y (in gen 1) was written to, all references from _y are considered roots. Extending the example, this could be a graph of objects, not just _z. (I'm also assuming that other object references adjacent to the object ref known as _y also become root-equivalent here - an unknown number since the number of references affected depends on the granularity of the data structure that tracks writes into higher generations.)

As far as possible optimizations, clearly an object undergoing finalization can't attempt to use managed objects. These objects may have been finalized, and may be hollowed-out zombies of objects that are incapable of fulfilling any sort of contracted behavior. And just as clearly, it's a type-safety/security issue to leave a reference dangling. But would it be possible to detect (using the previous example) for_y's reference to _z to be altered in some way so that the runtime could safely free the object previously known as _z? Would the cost be small enough to justify given that you would be able to free objects earlier? Until today I assumed so, and I believed that this was done - the MSDN article by Rico explains why this is not the case.

But this anchoring of unused references (_y causing _z to remain in-heap) is not unique to obects requiring finalization. Yes, a finalized object does persist longer than a non-finalized object, but that's a known side-effect of finalization. It seems to me that non-finalized object will also tend to anchor younger objects, albeit for a shorter period of time.

So what does this mean for you and me? The write barrier is a good place to avoid.

More on MSDN GC Article


Thinking about this a bit more... and maybe it's me that's buggy. I can see how the finalizer will tend to lock some objects, but I have a few questions. Bottom line: I agree with the perf issue described in the article, but still don't like the Dispose pattern example. Large post coming later today.

Thursday, April 24, 2003

Event Handling Strategies - Client Exceptions


So here's one of those problems that you run into when building distributed concurrent systems. Almost everyone I work with (me included) writes great code most of the time. Unfortunately, the odd case where we do something boneheaded can, on occasion, cause much grief - and trouble can come from unexpected sources - consider the humble .NET Framework event. Let's say that you create a component that exposes a public event. Given a sufficient number of clients wiring up event handling delegates, eventually one or more of these clients will allow unhandled exceptions to leak back into your component. This is a potential problem, since the typical naive pattern for invoking an event delegate chain is simply:
class Producer
{
	public event EventHandler Faucet;

	public void RaiseEvent()
	{
		if(Faucet != null)
			Faucet(this, EventArgs.Empty);
	}
}

If an exception flows back into this code it won't be handled, and your code will fall down and go boom.

Aha! you say. I'm no bumpkin! I've wrapped my code in a catch-all block, so that I'm immune to bad behavior to clients, like so:
public void RaiseEvent()
{
	try
	{
		if(Faucet != null)
			Faucet(this, EventArgs.Empty);
	}
	catch
	{
		// Handle exception here or just eat it.
	}
}

Unfortunately, while this code protects you, it doesn't protect any other (presumably well-behaved) clients that expect reliable event notification. If a client (incorrectly) throws an exception back to your component, pain does not flow to the offending party. Rather, other subscribers to the event will occasionally miss their event notifications. This is unfortunate, because although these clients are probably planning on doing evil things in the future, and may have even performed evil deeds recently, they are completely innocent of the current crime.

In order to ensure that all subscribers receive events even in the presence of bad behavior by other subscribers, you must catch exceptions separately for each invocation through the delegate chain. A boilerplate looks something like this:

public void RaiseEvent()
{
	if(Faucet != null)
	{
		// Pack arguments for event
		object[] args = new object[] {
					args[0] = this,
					args[1] = EventArgs.Empty
		};

		Delegate [] targets = Faucet.GetInvocationList();
		foreach(Delegate d in targets)
		{
			try
			{
				d.DynamicInvoke(args);
			}
			catch
			{
				// Handle exception
			}
		}
	}
}

MSDN Article Bugs


I missed this article when it was initially published last week, but MSDN has an introductory article on GC Basics and performance. Pretty good in some places, but there are a couple of errors that detract from the article.

The bit about finalization and performance is incorrect in one aspect. Although an object that requires finalization will lock any objects that it refers to, it will only do so while it is queued or it is being finalized. These are the only times that the object is root traceable. Consider:

  • In the period just before the need for finalization was discovered, all other strongly-owned objects referenced by the current offending object were not root traceable. If these objects were of newer generations, the expectation is that they have already been collected, and most likely finalized if need be.
  • At the point of initial collection any subobjects without finalizers should probably be released as an obvious optimization, as there seems to be no point in attempting to keep them around. The cost of tracing objects with finalizers, detecting cycles in the graph, and rescuing them from collection would be prohibitive. Further, as there's no way for the offending object to determine if the references are reliable while under finalization, they should be blown away ASAP.
  • While queued for finalization (and during finalization) the offending object is root-traceable. Any objects that haven't been released at this point also become root traceable. However, absent any race conditions, it's not at all clear to me how traceability during this relatively short period would serve to lock a graph of objects, since any non-finalized object in the graph of the same or lower generation has already been released.
  • After finalization of the offending object, the offending object's graph of strongly owned objects becomes non-traceable, and is once again subject to collection.

The article also improperly implements the Dispose pattern. Here's the fragment from the article:


 // Naive implementation - don't use!!

class X:  IDisposable
{
   public X(…)
   {
   … initialize resources … 
   }

   ~X()
   {
   … release resources … 
   }

   public void Dispose()
   {
// this is the same as calling ~X()
        Finalize(); 

// no need to finalize later
System.GC.SuppressFinalize(this); 
   }
};

Even ignoring the closing semi-colon for the class, this code doesn't properly separate the two disposal scenarios:

  • finalization invoked via a finalizer thread post collection, when managed references aren't reliable
  • disposal through the IDisposable interface, which compels disposal of managed objects that the current object maintains strong ownership over

Here's the basic boilerplate for the Dispose pattern:

public class MyResource: IDisposable
{
	bool _disposed = false;
	public void Dispose()
	{
		InternalDispose(true);
	}

	~MyResource()
	{
		InternalDispose(false);
	}

	private void InternalDispose(bool disposing)
	{
		if(disposing)
		{
			// Call Dispose on managed objects owned by this object
			_disposed = true;
			GC.SuppressFinalize(this);
		}
		// Free unmanaged resources
	}
}

The purpose of the _disposed field is to detect the case where a client attempts to use an object that's already been disposed, viz:

public void f()
{
	if(_disposed)
		throw new ObjectDisposedException("A pithy comment");

	// Do MyResource.f() ...
}

Wednesday, April 23, 2003

Werner's Blogging EuroRotor


Werner Vogels is blogging EuroRotor in Pisa. He covers a talk by Luca Cardelli on Polyphonic C#, which I've been drooling over for quite a while. The MSR page has some basic information including a feature chart, and a promise of a compiler someday. Hmm, if you work at MS do you get to sample this kind of stuff, or do you need to wait like everybody else...

Tuesday, April 22, 2003

Microsoft Smartphone Dev Kit


Oh yeah, definitely wanting one of these. I wonder if I can convince my daughters to buy me a license for Father's Day? No, wait - that's too far away.

Monday, April 21, 2003

Holy cow


Chris Sells moves north.

Dave Winer Smackdown


Love this quote from Mark Pilgrim: Oh, but announce a new but invalid RSS feed, and he’s all over that like Napalm on a fresh wound.
I really couldn't stand using Radio, for reasons layed out quite well by Werner (I'm a very happy Blogger Pro user now). It looks like there may be a wee bit of pent-up anger directed towards Dave Winer. Dave's of the tribe that consistently blames Microsoft for bugs and lack of adherence to standards, while producing a product that is, frankly, subpar.

The unique thing about this kind of train wreck is that you can follow the links as it unfolded, even if you weren't there when it happened. Updated: Just realized I didn't refer to the original post, which was from Dave.


Home