Previous Page
Next Page

13.5. Recoverable Failure

Throw exceptions on all failures, including recoverable ones.

All of the examples so far in this chapter have dealt with unrecoverable errors. If a file doesn't exist, can't be found, or can't be created, then there's not much more that a program can do except give up and throw an exception.

However, there are other kinds of resource acquisition failuressuch as failing to open a file that's currently locked by someone else, or being unable to fork a new process when your process limit has been reachedthat are not always hanging offenses. If the resource is likely to become available later, your application might choose to idle for a short period and try to acquire it again. It might even try that several times before giving up.

In such cases, it's tempting to report failure by returning undef:

    TRY:
    for my $try (1..$MAX_TRIES) {
        # Take care of locking of, and connection to, resource...
        $resource = acquire_resource($resource_id);

        # Got it...
        last TRY if defined $resource;

        

       # Report non-recoverable failure if no more tries
        croak 'Could not acquire resource' if $try == $MAX_TRIES;

        # Else wait for increasing random intervals to help resolve contention...
        nap( rand fibonacci($try) );
    }

    do_something_using($resource);

But, even when the expected failures are recoverable like this, it's still better to throw exceptions:


    TRY:
    for my $try (1..$MAX_TRIES) {
        
# If resource successfully acquired, we're done...
eval { $resource = acquire_resource($resource_id); last TRY; };
# Report non-recoverable failure if no more tries
croak( $EVAL_ERROR ) if $try == $MAX_TRIES;
# Otherwise, try again after an increasing randomized interval...
nap( rand fibonacci($try) ); } do_something_using($resource);

In this second version, acquire_resource( ) throws an exception on failure. That exception immediately terminates the execution of the eval block, so the last statement is skipped. The eval then captures the exception and neutralizes it, and the for loop continues on to take a nap. On the other hand, if acquire_resource( ) succeeds, it returns the appropriate resource descriptor, the assignment statement completes, the last statement is executed, and the for loop terminates.

So why use exceptions here? Especially since the code in the undef-on-failure version was slightly simpler.

The reason is precisely the same as in the previous three guidelines: because developers don't always check for failure. Instead of the careful retry strategies implemented in the previous example, they're just as likely to write:

    $resource = acquire_resource($resource_id);
    do_something_using($resource);

If acquire_resource( ) doesn't throw exceptions on failure, that code is broken, because it's going to propagate any failure down into do_something_using( ). So always throw exceptions on failure, even when that failure is potentially survivable.

    Previous Page
    Next Page