1

I want to process some very long running tasks using MDBs (hours), but MDBs are backed by JMS, which has some timeout (about 10 minutes in tomEE I think).

To make things worse, jobs are persistent too.

Since I can't catch the timeout exception on the MDB (can I?), I was considering the option of manually deal with the JMS consumer. Manually dealing with it can be done in two ways: synchronously or asynchronously.

Asynchronously I'll fall back to a stateless bean that implements MessageListener, and then I'll just not be able to treat the timeout again.

Synchronously, I guess, I can deal with the timeout in the catch block, but then, how can I implement a pool of instances/workers/whatever inside TomEE+ that are listening to a queue, waiting for a job to process? (remember, timeout here is not the time to wait for a message to appear in the queue, but the time to execute the long running task)

Leo
  • 6,480
  • 4
  • 37
  • 52
  • Why don't you fork off the long running process to a separate object ? – ramp Apr 15 '15 at 04:32
  • If there's a way to control the pool size of these separate objects, it could be a way. Do you want to try an answer? :-) – Leo Apr 15 '15 at 12:06

3 Answers3

3

Try to separate consuming JMS message and long running processing tasks

MDB could get message from queue and call stateless session bean with Asynchronous Method Invocation

MGolovanov
  • 65
  • 5
  • if I create a new transaction in the SLSB, will I get rid of the MDB timeout? – Leo Apr 15 '15 at 12:04
  • AFAIK No. The MDB will return only when the SLSB finishes. – ramp Apr 15 '15 at 13:12
  • @ramp - Hmm. Async call create new transaction. From EJB 3.1 spec. 4.5.3 Transactions Client transaction context does not propagate with an asynchronous method invocation. From the Bean Developer’s view, there is never a transaction context flowing in from the client. This means, for example, that the semantics of the REQUIRED transaction attribute on an asynchronous method are exactly the same as REQUIRES_NEW. – MGolovanov Apr 15 '15 at 20:21
  • @user3145155 - you are correct. I was responding to the OP's question about SLSB and missed the Asynchronous Method Invocation part in your answer. Essentially this then is the same solution I have outlined too in my answer. This comes off the shelf with EJB 3.1. I have edited my answer to remove the remark about the feasibility of this solution and also upvoted your answer. – ramp Apr 16 '15 at 04:37
3

Why don't you fork off the long running process to a separate object ? > If there's a way to control the pool size of these separate objects, it could be a way. Do you want to try an answer? :-)

You do not want an uncontrolled object growth over time ? Hmmm...I can see where you are going with that.

The only thing I can think of is use a FixedThreadPool from the concurrent framework. Spawn a thread for executing the long running task and ensure that the MDB returns immediately after handing over the work. Keep the thread pools controlled by profiling your application in a test environment.

Application servers like Weblogic and Websphere give you sophisticated frameworks like WorkManager for such tasks.

ramp
  • 1,256
  • 8
  • 14
0

The ManagedExecutorService exists in JEE7 to handle the execution of asynchronous tasks. By leveraging a Future interface implementation (see FutureTask) a task can be made cancellable as well. The following is meant to be an example, don't copy it 1:1 and use it in production. It has a memory leak in the map of future's by UUID is never cleaned up.

import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;
import java.util.concurrent.RunnableFuture;

import javax.annotation.Resource;
import javax.enterprise.concurrent.ManagedExecutorService;
import javax.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class AsyncTaskManager {

    @Resource
    private ManagedExecutorService mes;

    private Map<String, Future> futureByUUID = new ConcurrentHashMap<>();

    /**
     * Launch the provided Future implemented task.
     * @param runnable The runnable implementation to execute.
     * @param <R> The type of the task result returned.
     * @return The UUID that the executing ask is mapped to.
     */
    public <R> String launch (RunnableFuture<R> runnable) {
        String uuid = UUID.randomUUID().toString();
        mes.submit(runnable);
        futureByUUID.put(uuid, runnable);
        return uuid;
    }

    /**
     * Retrieve a future instance by it's UUID.
     * @param uuid The uuid of the future.
     * @param <T> The type of the future's result.
     * @return The future instance.
     */
    @SuppressWarnings("unchecked")
    public <T> Future<T> getByUUID (String uuid) {
        return (Future<T>)futureByUUID.get(uuid);
    }
}

There are a whole set of interfaces and classes that can be combined to change the behavior of your task within the executor service. For example the ManagedTask interface provides access to some of these behaviors.

The ManagedExecutorService is leveraged for the @Asynchronous EJB method annotation as mentioned in the answer by MGolovanov. The task creation and Future instance creation is handled by the EJB framework.

justin.hughey
  • 1,246
  • 15
  • 16