Software Mechanics
Why do we even have that lever?

Wrapping Async callbacks

June 12, 2009 16:22 by chris

We’ve started adding asynchronous support to the Data Access block. This has been a highly requested feature, so we put it fairly high on our backlog. At first glance, this looked pretty easy. Just add a couple of methods to the SqlDatabase class:

public IAsyncResult BeginExecuteReader(DbCommand command, string commandText, AsyncCallback callback, object state);
public IDataReader EndExecuteReader(IAsyncResult ar);

 

(There are another bazillion overloads, but for this discussion let’s stick to these, ok?)

Starting out

Enterprise Library does a lot of things under the hood. In particular, we have a bunch of optional instrumentation. As such, we want to track things like which command was executed, what the start time was, and a bunch of other stuff. So we decided we needed our own implementation of IAsyncResult. We implemented DaabAsyncResult to wrap around the one returned by ADO.NET, stuffed our extra stuff in there, and read it back out in EndExecuteReader. The two methods started out looking like this:

public IAsyncResult BeginExecuteReader(DbCommand command, string commandText,
    AsyncCallback callback, object state)
{
    IAsyncResult innerResult = ((SqlCommand)command)
	.BeginExecuteReader(callback, state, CommandBehavior.Default);
    return new DaabAsyncResult(innerResult, ... other stuff ...);
}

public IDataReader EndExecuteReader(IAsyncResult ar)
{
    var result = (DaabAsyncResult)ar;
    ... do other stuff ...
    
    return result.InnerResult.EndExecuteReader(result.InnerResult);
}

Pretty straightforward, really. But there are a ton of gotchas here. Let’s take a look at what’s wrong with this code.

Replacing the IAsyncResult object

The first one to pop out is in this line:

var result = (DaabAsyncResult)ar; 

This will end up throwing an InvalidCastException. Why? Because we’re not invoking the callback, ADO.NET is. And it’s not passing our async result object, it’s passing its own, unwrapped one. Obviously we need a way to get our result object in there.

There’s no way that I could find to override how this process works, so we need to cheat a little. The solution I ended up with was to pass, not the original callback, but a small wrapper (using a lambda function) so we can do the switch between the two. This lambda looks something like this:

var wrapperCallback = ar => {
    callback(GetDaabAsyncResult(ar));
}

The obvious next step is, of course to implement GetDaabAsyncResult. This is where things get tricky. Where do we get a hold of the async result? It’s the return value from BeginExecuteReader. When do we have it? After BeginExecuteReader returns, of course. When do we set up the callback? Before calling BeginExecuteReader. Uh oh…

Luckily, C#’s lexical closure features comes to our rescue. What we can do is store the async result in a local variable. Reference it from the lambda, and the value will be captured. So the body of BeginExecuteReader now looks like this:

    DaabAsyncResult result = null; // need null to satisfy definite assignment rules

    var wrapperCallback = ar => {
        callback(result);
    };

    var innerResult = command.BeginExecuteReader(wrapperCallback, state, ...);
    result = new DaabAsyncResult(innerResult, ...);
    return result;

And we’re done! Our user’s callbacks get the right IAsyncResult object, happily wrapped and ready to go. Sadly, not done yet…

Off to the races

Anyone who’s done threading is familiar with race conditions. For those who aren’t, a race condition is a section of code that will produce different results depending on which thread runs in which order. And we’ve got a whopper of a race condition here. Do you see it? I’ll give you a hint – what happens if the wrapper callback runs after BeginExecuteReader returns but before result get assigned?

Yes, it’s a small window, but it’s a statistical guarantee that you’ll hit it every time you’re doing a demo for a huge client or a VC or an Admiral or something (I speak from bitter experience here, trust me). So we need to prevent the wrapper callback from accessing the value of result until we know, beyond a shadow of a doubt, that it’s been set.

Basically, we need one thread to wait for another one to do something. My initial though was to use an Event object. But thinking about that, it didn’t seem right – Events are kernel objects, they need to be disposed, and we may have a lot of these things (one per call to BeginExecuteReader to be precise). Luckily, there’s a lighter weight solution - .NET locks.

We need to introduce a lock so that the wrapper won’t execute until we know that result has been set. Luckily, that’s pretty easy. The code looks like this now:

    DaabAsyncResult result = null;
    var padlock = new object();

    var wrapperCallback = ar => {
        lock(padlock) { }
        callback(result);
    };

    lock(padlock)
    {
        var innerResult = command.BeginExecuteReader(wrapperCallback, state, ...);
        result = new DaabAsyncResult(innerResult, ...);
    }
    return result;

Thanks to padlock, the wrapperCallback might start before result is set, but it won’t continue until that lock is released, and it won’t be until result is safely set. I know the “lock(padlock) { }” line looks a little weird, but it’s legitimate. Calling the callback doesn’t need to be within the lock, and I’ve had many years of “make your lock regions as short as possible” drilled into my brain.

So, there we go, race condition solved. Ship it! Right?

One thing at a time

Unfortunately, there’s another wrinkle in this whole thing. It is allowed for implementers of the async pattern to actually complete the entire operation, synchronously, on the same thread that called BeginExecuteReader, and call the callback before returning the async result. While I have no idea if ADO.NET can actually do that, nothing in the docs says it can’t, and even if it doesn’t now, that doesn’t mean that it won’t in future versions. So we have to handle this case.

This torpedos our design in a couple of ways. First, in synchronous completion, the locks don’t block. Instead it’s just a recursive acquisition of a lock by the same thread multiple times, a completely normal and legal thing to do. In fact, if this wasn’t allowed we’d actually deadlock here, which would be even worse. But this time, since we’re on the same thread, there’s no possible way that the callback can wait until after result has been set.

Luckily, there’s an easy way to tell if this happened – the IAsyncResult.CompletedSynchronously property will be true. We much not invoke the callback until we know result has been set. So let’s do exactly that. In our wrapper, if we completed synchronously we know result hasn’t been set, so we don’t invoke the callback. Instead, we do it manually. And we end up at this:

DaabAsyncResult result = null; 
var padlock = new object();
var wrapperCallback = ar => {
    lock(padlock) { }
    if(!ar.CompletedSynchronously)
    {
        callback(result); 
    }
};

lock(padlock) 
{
    var innerResult = command.BeginExecuteReader(wrapperCallback, state, ...);
    result = new DaabAsyncResult(innerResult, ...);
}

if(result.CompletedSynchronously)
{
    callback(result);
}

return result; 

Wrapping Up

So there you have it. This recipe should work for wrapping any async operation that implements the standard async pattern. It’s a little involved to set up, granted. But the only other approach I could think of was to put my callback on a threadpool thread, thus eating two threads for one async operation. Why do that when one will do?


Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Tags:
Categories:
Actions: E-mail | Permalink | Comments (5) | Comment RSSRSS comment feed

Comments

Comments are closed