Tuesday 31 March 2009

Resource Acquisition Is Initialization

Let's take a moment away from Java to consider how C++ addresses the problem of resource handling. The usual technique is known as Resource Acquisition Is Initialization. The aspect of this pattern relevant to this discussion is that you wrap up the resource handling in a class which closes the resources in its destructor. In C++ you can declare the object as a stack variable, and thus whether the client code throws an exception or returns normally, the destructor is always called and the resource closed. In addition note that destructors cannot throw exceptions, so any exception thrown by close() would be ignored - consequently in C++ close() does not throw an exception. This can be easily achieved with an API e.g. clients calling a flush() function (which can throw) followed by close().

With these two differences from Java (controllable object lifetimes & close() not throwing exceptions) we find making exception-safe client code becomes trivial:

void foo() {
MyResourceWrapper w;
//do some stuff with w

Where we have a class:

class myResourceWrapper
{
public:
    myResourceWrapper() : r()
    {
    }
 
    ~myResourceWrapper()
    {
        r.close();
    }
 
    void bar()
    {
        //function to manipulate r...
    }
 
private:
    myResource r;
 
    // prevent copying and assignment; not implemented
    myResourceWrapper(const myResourceWrapper&);
    myResourceWrapper& operator= (const myResourceWrapper&);
};

Note that using multiple resource wrappers in a single function provides no extra complications (no equivalent to the nested try blocks) - each class that needs closing is wrapped once and then can be used in an exception-safe fashion everywhere.

Simple Resource Handling in Java

Now we've seen how not to do it, let's see the simple solution and its obvious pitfalls.

void foo() throws IOException {
SomeResource r = new SomeResource();
try {
//do some stuff with r
}
finally {
r.close();
}

This is a trivial solution, in all likelihood good enough, but it has an obvious flaw: if close() throws an exception, then this masks any exception that occured in the try block. This is not a major flaw, since the code still throws an IOException from the correct function, but it may hinder any code handling the error, or give misleading error messages or diagnostics.

For multiple resources we need nested try blocks:

void foo() throws IOException {
SomeResource r1;
try {
//open r1 etc.
AnotherResource r2;
try {
//open r2
}
finally {
r2.close();
}
finally {
r1.close();
}
}

This has the additional problem that the code is looking rather convoluted.

Resource Leak Anti-Patterns in Java

Guarding against resource leaks in Java is fraught with peril. I've seen the following three well-known anti-patterns used by experienced Java developers, so it's well worth starting any discussion on the correct way to manage resources by restating how not to do it!

Anti-Pattern #1: Close resources in your finalizer

You put all your resources as member variables of a class, and then close them in the finalizer, just as if it were a C++ destructor.
The fundamental problem with this approach is that finalizers are only called when the VM is freeing up memory, so you're hoping that the system runs out of a plentiful resource (memory) before it runs out of a scarce resource.
Regardless of what cludges you attempt (you were hoping that System.gc() or System.runFinalization() might help out weren't you?) this approach is fatally flawed from the outset.

Anti-Pattern #2: Mishandling multiple calls to close()

The standard Java approach is to put your resource handling code in a try block, and the resource closing in a finally block.

void foo() throws IOException {
SomeResource r1;
AnotherResource r2;
try {
//open r1 & r2 and do some other stuff that might throw an exception
}
finally {
r2.close();
r1.close();
}
}

However, close() can throw an exception, and if r2.close() throws an exception, then r1 will never get closed...

Anti-Pattern #3: Supressing close() exceptions

It's bad form to throw exceptions in a finally clause since it both masks any exception thrown in the try block and also causes the rest of the finally clause not to run, so you often see the following solution:

void foo() throws IOException {
SomeResource r = new SomeResource();
try {
//do some stuff with r
}
finally {
try{ r.close(); } catch(IOException e) { /*swallow e silently*/ }
}
}


Normally this works nicely - until close() throws an exception after the try block has been succesful, which causes the error to be silently ignored!

This leads to the worse class of defect (as per my last post) - one that easily goes undetected until it causes a failure in the real world, and also one that is very difficult to track down.

Obvious Bugs in Code

I have been reminded recently of a fundamental rule in programming & design which nevertheless has been sidelined or undervalued so often that it's worth repeating and repeating and repeating again.

You should aim to write/design/structure your code/language/framework so that bugs are obvious.

Bugs can be spotted at various points, a slight over-simplification would be to characterise these stages as:
  • Defect is seen by looking at the code
  • Defect gives rise to a compiler error
  • Defect gives rise to a compiler warning
  • Program falls over at run-time at (or immediately after) the erroneous code.
  • Erroneous behaviour caught by automated testing
  • Erroneous behaviour caught later (or perhaps never)
The earlier a bug is spotted, the less impact it has, so the aim should always be to pick it up as high up this list as possible.

I was reminded of this issue by considering how best to guard against resource leaks in Java. There are any number of suggestions online for how to do this, and most of the solutions make your code rather complex (so errors are easy to introduce, but hard to spot) while at the same time errors would all fall in to the "detect at runtime (hopefully by us rather than our customers)" category.

In my first set of posts I'm going to consider this issue, always keeping this fundamental rule in mind.