Tuning suspended AsyncResponse and Thread-pools in Glassfish 4

Posted: July 26, 2013 in Glassfish, Java

I am experimenting with Glassfish 4 to prepare in order to move some application from J2EE6 to J2EE7. Glassfish 4 works with J2EE 7 and introduces some new concepts. The one which we are investigating today is the use of @Asynchronous and @Suspended in REST resources.

The use of the asynchronous annotations is pretty well specified for beans but there are some pitfalls when it comes to REST services. Let’s go through an example and check the behavior of a REST service with asynchronous methods as we go.

The use case is the following. We define a Resource and one Bean. The resource is a typical REST-resource with only one GET-method. The bean is a normal, stateless session bean which performs a long running task. This bean is pretty straightforward. We do not annotate the methods of the bean as asynchronous.

@Stateless
public class LongSession {
   public String doLongTask() {
       try {
           // do a long task
           Thread.sleep(10*1000);
       } catch (InterruptedException ex) {
           // ignore
       }
       
       return "done";
   }
}

The first resource we build is pretty simple. We create an @Stateless resource and inject a @Suspended AsyncResponse. AsyncResponse takes care of the asynchronous response when the results become available.

@Path("as")
@Stateless
public class AsResource {

    @EJB
    private LongSession longSession;
    
    @GET
    @Produces(MediaType.TEXT_HTML)
    @Asynchronous
    public void getXml(@Suspended AsyncResponse resp) {
        System.out.println("do a long task in thread: " + Thread.currentThread().getName());
        String result = this.longSession.doLongTask();
        Response r = Response.ok("this is the " + result).build();
        resp.resume(r);
    }
 
}

When we open the browser and point to the URL of this resource we will see that the request is blocked for 10 seconds! But that is nothing new. Now, the problem is that we want to know how many threads are available to serve these requests. We are using a stateless bean, so we use the thread-pool of the ejb-container. This has some implications. When we use the http-thread-pool we will basically the same behavior, but we are not interested in the request thread. I want to scale out the ejb-pool. Adding beans to the pool is apparently not enough.

When you press F5 continuously in the browser, you will see something like “INFO: do a long task in thread: __ejb-thread-pool1” in the log. This number counts up and exceeds the threads in the http-thread-pool thanks to the AsyncResponse and @Suspended. But you will see (in a freshly installed domain) that the thread-pool does not exceed the limit of 16 even though you have max. 64 beans in your pool. We need to finetune the thread-pool of the ejb-container. But, you won’t find any properties in the administrator console. You need to add them yourself. Open the domain.xml of your domain and add the following lines:

     <ejb-container max-pool-size="64" steady-pool-size="7">
        <property name="thread-core-pool-size" value="10"></property>
        <property name="thread-max-pool-size" value="20"></property>
        <property name="thread-queue-capacity" value="25"></property>
        <ejb-timer-service></ejb-timer-service>
      </ejb-container>

Now, rerun your application. You will see that the thread-pool goes up to 10 when you press F5 in the browser without holding it down. It seems to stagnate on 10 although you kinda specified a max-pool-size of 20. When you continuously press F5 you will suddenly see the threads go up to 20 before throwing an java.util.concurrent.RejectedExecutionException. Nice, but what the hell happened?

Let’s dig deeper in the documentation of the thread pools:

thread-core-pool-size: Specifies the number of core threads in the EJB container’s common thread pool. The default value is 16. Great, there we have our number 16. Setting this to 10 oder 100 will change the actual number of threads doing some work.

thread-max-pool-size: Specifies the maximum number of threads in the EJB container’s common thread pool. The default value is 32. Nice, increasing this to 100 will be the maximum number of threads we can use? Yes and no. You have to consider the default-value of thread-queue-capacity.

thread-queue-capacity: Specifies the size of the thread pool queue, which stores new requests if more than thread-core-pool-size threads are running. The default value is the Integer.MAX_VALUE.

Here starts the confusion. Your queue-capacity is way too high. Pressing F5 will never reach MAX_VALUE, so your core-pool-size never change nor scale. You must limit your capacity first before the thread-pool is scaled with maximal max-pool-size threads. In our example, we will scale when we reach 25 waiting requests. It will scale up to 20. When all threads are used in parallel the container throws an exception.

In the past SUN declared correctly: “That is exactly how it is supposed to behave. First the threads grow to coreSize, then the queue is used, then *if* the queue fills up then the number of threads expands from coreSize to maxSize. Hence if you use an unbounded queue the last part never happens. This is all described in the documentation. If you want an unbounded queue but more threads then increase the core size. Otherwise consider whether a bounded queue is more suitable to your needs.”

Some extra information can be found here https://java.net/jira/browse/GLASSFISH-17735 and http://docs.oracle.com/javase/6/docs/api/java/util/concurrent/ThreadPoolExecutor.html.

Leave a comment