Parallel task execution in J2EE using the Work Manager specification
Pages: 1, 2, 3, 4

Exception Handling

So far we have assumed that our jobs always execute successfully and never throw exceptions. In reality, of course, this is not always the case. In our imaginative use case there could be a network problem preventing translation from successfully executing, or the words could be misspelled or missing from the dictionary. What happens when we throw an exception from the run() method of the Work object? First of all, note that the run() method signature doesn't define any checked exceptions, therefore we have to use an instance of RuntimeException or any of its subclasses. To explore the use of exceptions, let's first define a TranslationException class:

public class TranslationException extends RuntimeException {

    public TranslationException(String message) {
        super(message);
    }

    public TranslationException(String message, Throwable cause) {
        super(message, cause);
    }
}

Now we'll create yet another implementation of Translator, which will throw a TranslationException randomly in approximately 50 percent of the cases.

public class DummyTranslatorWithError extends AbstractTranslator {
    private final long delay;
        
    public DummyTranslatorWithError(String source, long delay) {
        super(source);
        this.delay = delay;
    }
        
    public void translate() {
        // delay to simulate network call
        try {
            Thread.sleep(this.delay);
        } 
        catch (InterruptedException ignore) { }

        // randomly throw Exception
         
                        
if (Math.random() > 0.5) {
            throw new TranslationException("Cannot translate "+getSource());
        }
        this.translation = this.source+"_tr";
    }
}
                      

If we replace DummyTranslator with DummyTranslatorWithError in the ServletParallel implementation and run it, we'll sometimes get exceptions at the time results are retrieved. What happens is that the container intercepts any exception thrown from a job, and throws it again when WorkItem.getResult() is called. We can use this knowledge to modify the ServletParallel implementation to accommodate error processing:

protected List doTranslation(List input) throws Exception {
    ...
    TranslatorWorkListener listener = new TranslatorWorkListener();
    for (Iterator iter = input.iterator(); iter.hasNext();) {
        String source = (String) iter.next();
        Translator translator = new DummyTranslatorWithError(source, 10 * 1000);

        // schedule
        Work work = new WorkTranslatorWrapper(translator);
        WorkItem workItem = this.workManager.schedule(work, listener);
        jobs.add(workItem);
    }
                
    logger.info("All jobs scheduled");
    this.workManager.waitForAll(jobs, WorkManager.INDEFINITE);
    
    // extract results
    for (Iterator iter = jobs.iterator(); iter.hasNext();) {
        WorkItem workItem = (WorkItem) iter.next();
         
                        
try {
            // if the Work threw an exception during run 
            // then the exception is rethrown here
            Translator translator = (Translator) workItem.getResult();
            result.add(translator.getTranslation());
        }
         
                        
catch (Exception e) {
            result.add(e.getMessage());
        }
    }
    return result;
}
                      

Note that if Work threw an exception during execution, it's not possible to retrieve the original Work implementation by executing the WorkItem.getResult() call. If there is a need to correlate a failed job with the original Translator, the application could maintain a Map between WorkItem objects and Translator objects, in almost the same way as was done for TranslatorWorkListener.

Security and Transactional Context Propagation

It's worth noting that the current version of the Work Manager specification (1.1) does not cover the propagation of security and transactional contexts from the caller thread into scheduled jobs. In all current implementations the security context is propagated but not the transactional context, which makes sense if you think what should happen if an application starts a new transaction, schedules jobs, and commits transactions without waiting for jobs to complete. On the other hand, jobs are permitted to start new transactions and commit or roll back the transactions on their own. Jobs can also look up objects from the JNDI comp: namespace of their parent.

Conclusion

In this article we created a working example of using the Work Manager specification to achieve parallel execution of tasks in the servlet container. Everything discussed above can be used in an EJB container as well. Now, with the coming release of BEA WebLogic 9.0, developers have a convenient, simple, yet powerful API to start and run any number of parallel processes from the main execution thread, as well as a flexible synchronization mechanism and lifecycle events support.

We haven't covered all aspects of the specification, even in the area related to WorkManager. For example, there is optional support for remote job execution. If WorkManager implementation supports Remoteable WorkManager, then the Work can be sent to a remote member of the application cluster for execution. This functionality is not yet supported by WebLogic or WebSphere but looks promising if load balancing is desired.

There is also support for Timers, which is more flexible than existing timer specifications in JMX or the timer service in EJB 2.1. We hope to cover this in subsequent articles.

Additional Reading

  • JSR 237 - Work Manager for Application Servers

Dmitri Maximovich is an independent consultant specializing in software design, development, and technical training. He has more than twelve years of industry experience and has been involved with J2EE since its inception.