If you’re getting mysterious IOErrors with URLLoader, URLStream, or Socket, this might be why

MailBrew was the first application I wrote with the new global error handler feature in AIR 2 and Flash Player 10.1. Whenever an unhandled error is thrown, the application opens a utility window with some details and a request to send the information to me so I can get the bug fixed for the next version. I’m a pretty conscientious coder — even in areas where ActionScript 3 doesn’t require you to be — so I wasn’t expecting many reports, but I got several relating to mysterious IOErrors. Unfortunately, errors caught with the global error handler don’t have stack traces, so debugging wasn’t going to be easy. As far as I could tell, I was already catching and registering for IOErrors and IOErrorEvents in all appropriate places, so I initially had no idea what was going on.

Fortunately, Daniel Koestler was finally able to reproduce the error in ADL and get a stack trace. At first, however, it didn’t seem to make any sense. The line throwing the uncaught error was this:

this.urlLoader = new URLLoader();

It took me a minute to figure it out, but now I think I know what’s going on. I believe when the old URLLoader was being destructed (when this.urlLoader already referenced a URLLoader instance), its close() function was implicitly being called. This was usually fine, but in some circumstances, close() can cause a stream error (a type of IOError) which I never had the opportunity to catch. So even though I was registering for IOErrorEvents, and I was calling close() in a try/catch block, it was possible for the close() function to be called in a way that I could not anticipate.

I’m think I’m going to file this as a bug and ask that IOErrors not be thrown in circumstances where close() is called implicitly (in other words, the code that calls close() in the object’s destructor should swallow the exception rather than allow it to propagate), but until it’s fixed, here’s my work-around:

private function start():void
{
    this.dispose();
    this.urlLoader = new URLLoader();
    this.urlLoader.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
    this.urlLoader.addEventListener(Event.COMPLETE, onComplete);
    this.urlLoader.addEventListener(HTTPStatusEvent.HTTP_RESPONSE_STATUS, onResponseStatus);
    // Go on to use the loader...
}

public function dispose():void
{ 
    if (this.urlLoader != null)
    {
        try
        {
            this.urlLoader.close();
        }
        catch (e:IOError)
        {
            // No problem. We're getting rid of it, anyway.
        }
        this.urlLoader.removeEventListener(IOErrorEvent.IO_ERROR, onIOError);
        this.urlLoader.removeEventListener(Event.COMPLETE, onComplete);
        this.urlLoader.removeEventListener(HTTPStatusEvent.HTTP_RESPONSE_STATUS,
                                           onResponseStatus);
        this.urlLoader = null;
    }
}

As you can see, I’m now making sure to call close() explicitly myself before the runtime has the opportunity to call it implicitly whenever I assign a new instance of a URLLoader to a class-level variable. My guess is that there’s a lot of code out there prone to throwing this error, but because the global error handler is so new, most of us just don’t know it yet.