Parallel task execution in J2EE using the Work Manager specification
by Dmitri Maximovich
05/16/2005
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.