Kennis Blogs EJB 3.1: Concurrency Management and (Avoiding) Synchronization

EJB 3.1: Concurrency Management and (Avoiding) Synchronization

At Avisi we use a custom-built EJB 3-based application for scheduling and running (automated) regression tests. This involves a queue from which objects are taken. These objects contain metadata describing the tests to execute. I won't go into detail as to why we aren't using Apache ActiveMQ (or a similar library) for this purpose, but I can say that we didn't need distributed test-executing minions at that time.

 

Keeping KISS in mind (the principle, not the band), the model for this queue-and-execute feature looks something like this:

 

The Scheduler is responsible for polling the queue and instructing the Executor to run specific tests. Both tasks are implemented in the Executor for easier queue synchronization. Since the execution of a test is a rather long, we don't want to wait for completion (that wouldn't allow us to run tests in parallel). To parallelize the 'execute(test)' method, we use the @Asynchronous annotation introduced in EJB 3.1. Since the nature of our Scheduler is... well, a scheduler, we want it to perform its task about every second. This is achieved using the @Schedule annotation (see the example below). Don't forget to annotate both classes with @Singleton.

import javax.ejb.*;
import java.util.logging.Logger;

@Singleton
public class Scheduler {
private final Logger log = Logger.getLogger(getClass().getName());

@EJB
private QueueExecutor queueExecutor;

@Schedule(minute = "*", second = "*", hour = "*", persistent = false)
public void tick() throws Exception {
log.info("calling poll()");
queueExecutor.poll();
log.info("calling execute()");
queueExecutor.execute();
}
}

import javax.ejb.*;
import java.util.logging.Logger;

@Singleton
public class QueueExecutor {
private final Logger log = Logger.getLogger(getClass().getName());

public void poll() {
log.info("poll()");
}

@Asynchronous
public void execute() throws Exception {
log.info("Execution starting...");

// Perform some lengthy task
Thread.sleep(5000);

log.info("Execution has ended.");
}
}

All set! Now we're ready to run those tests in parallel! But while actually deploying these EJB's, it become apparent that there's nothing parallel about our setup at all. The problem is in fact that 'poll( )' isn't called before 'execute(test)' has completed its task (invoked by the previous scheduler iteration). The issue is that session beans are thread-safe and so are singleton session beans. So, access to both 'poll( )' and 'execute(test)' is synchronized on class-level, which means our observation that 'poll( )' isn't called before 'execute(test)' finishes, is correct.

Bean synchronization can be controlled using the @ConcurrenyManagement annotation, which defaults to 'CONTAINER'. The other option is 'BEAN', which means the 'bean developer is responsible for managing concurrent access to the bean instance'.

Thus, the solution to our not-so-concurrent problem is simply setting @ConcurrenyManagement type to 'BEAN':

import javax.ejb.*;
import java.util.logging.Logger;

@Singleton
@ConcurrencyManagement(value = ConcurrencyManagementType.BEAN)
public class QueueExecutor {
private final Logger log = Logger.getLogger(getClass().getName());

public void poll() {
log.info("poll()");
}

@Asynchronous
public void execute() throws Exception {
log.info("Execution starting...");

// Perform some lengthy task
Thread.sleep(5000);

log.info("Execution has ended.");
}
}

If you deploy this to an EJB 3.1 enabled container (like Glassfish v3.1.1, in our case), you will be able to run tests in parallel (just don't forget to check up on your container's thread pool).
 
Of course, another way to solve the problem is by separating responsibilities further, which would mean our Executor wouldn't be in charge of managing the queue in the first place.