Saturday, December 30, 2006

Reader mail: making tasks noncancelable, and polling for interruption

I frequently get e-mail feedback on my articles on IBM developerWorks.  Unfortunately, I can rarely reply to them because no one ever leaves their e-mail address.  The majority of e-mails I get are erroneous corrections (not that I don't make mistakes -- I make plenty, and those get pointed up too -- its just there are even more people out there who are really really sure they're always right, but aren't), which I'd be happy to respond to if anyone ever left their e-mail... Here's one from Dealing with InterruptedException.  Listing 6 shows an example of how to make an operation noncancelable by deferring interruptions until the operation completes. 

public Task getNextTask(BlockingQueue queue) {
    boolean interrupted = false;
    try {
        while (true) {
            try {
                return queue.take();
            } catch (InterruptedException e) {
                interrupted = true;
                // fall through and retry
            }
        }
    } finally {
        if (interrupted)
            Thread.currentThread().interrupt();
    }
}

The anonymous reader asks: shouldn't this be "while (!interrupted)"?  Won't the loop never terminate?  The idea behind this example was to address the problem of "what if we have an operation that should not be interruptible, but is composed using interruptible steps?"  We will have to ignore the interruptions when they occur and retry the interrupted operation -- but the key is we want to remember if there has been an interruption, so we can restore the interrupted status after we complete our noncancelable operation.  That way, we don't throw away the information that someone requested cancellation, we just defer the cancellation request until after the noncancelable operation operation completes. 

A related issue that is often asked is how often we should check for interruption.  Listing 3 shows a typical interruptible operation wrapped in a Runnable.  When this happens, in order to not throw away the evidence that an interruption was requested, we have to re-set the interrupted status with Thread.currentThread().interrupt(). 

public class TaskRunner implements Runnable {
    private BlockingQueue queue;
    public TaskRunner(BlockingQueue queue) {
        this.queue = queue;
    }
    public void run() {
        try {
             while (true) {
                 Task task = queue.take(10, TimeUnit.SECONDS);
                 task.execute();
             }
         }
         catch (InterruptedException e) {
             // Restore the interrupted status
             Thread.currentThread().interrupt();
         }
    }


It is often asked: shouldn't the loop header be while (!Thread.currentThread().isInterrupted()), instead of while(true)? This one is a little more subtle.  We can look at this from several perspectives: correctness, responsiveness and performance. 

From a correctness perspective, the existing code is correct, because (reasonably coded) interruptible blocking methods check if the interrupted status is set on method entry, and immediately throw InterruptedException() if it is.  Similarly, the existing approach is effectively as responsive as the suggested alternative, because the first step in the while loop is to call an interruptible blocking method.  (If substantial computation occurred between loop entry and the first interruptible blocking method call, responsiveness might give us a reason to test the interrupted status in the loop header as well -- and maybe other places throughout the loop too.) 

What about performance?  As with most performance questions of this sort, the answer is "We don't have enough information to tell".  Testing the interrupted status in the loop header costs a little more (because the test is repeated in the loop header and in the blocking take() call), but in the case that the interrupted status is set, saves us the cost of instantiating and catching the exception thrown from take() when it finds the interrupted status set on entry.  Which is a performance win will depend on how often the operation is actually interrupted -- and we usually don't have this information when coding general-purpose library code. 

When in doubt, do the thing that makes the code simplest, cleanest, and most readable.

No comments:

Post a Comment