2/10/2011

How to create and look up thread pool resource in GlassFish

In order to create a custom thread pool task executor resource, one needs to implement 2 classes: the thread pool class, and its object factory class.

In my implementation, the thread pool class, test.ThreadPoolExecutor is simply a subclass of java.util.concurrent.ThreadPoolExecutor. It is implemented as a singleton so every lookup and injection always return the same instance.

package test;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

public class ThreadPoolExecutor extends java.util.concurrent.ThreadPoolExecutor {
static final int defaultCorePoolSize = 5;
static final int defaultMaximumPoolSize = 10;
static final long defaultKeepAliveTime = 10;
static final TimeUnit defaultTimeUnit = TimeUnit.MINUTES;
static final BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<Runnable>();
private static ThreadPoolExecutor instance;

private ThreadPoolExecutor() {
super(defaultCorePoolSize, defaultMaximumPoolSize, defaultKeepAliveTime, defaultTimeUnit, workQueue);
}

synchronized static ThreadPoolExecutor getInstance() {
if (instance == null) {
instance = new ThreadPoolExecutor();
}
return instance;
}
}
The factory class, test.ThreadPoolExecutorFactory, is required to implement javax.naming.spi.ObjectFactory. It also implements a GlassFish-specific interface com.sun.appserv.server.LifecycleListener so that it can also be registered as a GlassFish lifecycle module. Upon receiving a server termination event, this class will shutdown the thread pool resource.
package test;

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.concurrent.TimeUnit;

import javax.naming.Context;
import javax.naming.Name;
import javax.naming.NamingException;
import javax.naming.RefAddr;
import javax.naming.Reference;

import com.sun.appserv.server.LifecycleEvent;
import com.sun.appserv.server.ServerLifecycleException;

public class ThreadPoolExecutorFactory implements javax.naming.spi.ObjectFactory,
com.sun.appserv.server.LifecycleListener, java.io.Serializable {
public Object getObjectInstance(Object obj, Name name, Context nameCtx,
Hashtable<?, ?> environment) throws Exception {
ThreadPoolExecutor tp = ThreadPoolExecutor.getInstance();
try {
Reference reference = (Reference) obj;
Enumeration<?> enumeration = reference.getAll();
TimeUnit timeUnit = ThreadPoolExecutor.defaultTimeUnit;
long keepAliveTime = ThreadPoolExecutor.defaultKeepAliveTime;
while (enumeration.hasMoreElements()) {
RefAddr refAddr = (RefAddr) enumeration.nextElement();
String pname = refAddr.getType();
String pvalue = (String) refAddr.getContent();
if ("corePoolSize".equalsIgnoreCase(pname)) {
tp.setCorePoolSize(Integer.parseInt(pvalue));
} else if ("maximumPoolSize".equalsIgnoreCase(pname)) {
tp.setMaximumPoolSize(Integer.parseInt(pvalue));
} else if ("timeUnit".equalsIgnoreCase(pname)) {
timeUnit = TimeUnit.valueOf(pvalue);
} else if ("keepAliveTime".equalsIgnoreCase(pname)) {
keepAliveTime = Long.parseLong(pvalue);
} else if ("allowCoreThreadTimeOut".equalsIgnoreCase(pname)) {
tp.allowCoreThreadTimeOut(Boolean.parseBoolean(pvalue));
} else if ("prestartAllCoreThreads".equalsIgnoreCase(pname)) {
if (Boolean.parseBoolean(pvalue)) {
tp.prestartAllCoreThreads();
}
} else {
throw new IllegalArgumentException("Unrecognized property name: " + pname);
}
}
tp.setKeepAliveTime(keepAliveTime, timeUnit);
} catch (Exception e) {
throw (NamingException) (new NamingException()).initCause(e);
}
return tp;
}

public void handleEvent(LifecycleEvent event) throws ServerLifecycleException {
if (event.getEventType() == LifecycleEvent.TERMINATION_EVENT) {
ThreadPoolExecutor tp = ThreadPoolExecutor.getInstance();
System.out.println("About to purge and shutdown " + tp + ", active thread count: "
+ tp.getActiveCount());
tp.purge();
tp.shutdown();
}
}
}
Copy 2 class files to $GLASSFISH_HOME/domains/domain1/lib/classes directory, with package name, and restart domain:
ls $GLASSFISH_HOME/domains/domain1/lib/classes/test/
ThreadPoolExecutorFactory.class ThreadPoolExecutor.class

$ asadmin restart-domain
Next, create the thread pool resource, and register lifecycle module. It can also be done in admin console, in a more user-friendly manner.
$ asadmin create-custom-resource --restype test.ThreadPoolExecutor --factoryclass test.ThreadPoolExecutorFactory --description "A ThreadPoolExecutor backed by LinkedBlockingQueue" --property corePoolSize=6:maximumPoolSize=50:keepAliveTime=4:timeUnit=MINUTES:allowCoreThreadTimeOut=true:prestartAllCoreThreads=true concurrency/TP
Command create-custom-resource executed successfully.

$ asadmin create-lifecycle-module --classname "test.ThreadPoolExecutorFactory" --failurefatal=true concurrency/TP-shutdown
To list and delete custom resources and lifecycle modules:
$ asadmin list-custom-resources

$ asadmin list-lifecycle-modules

$ asadmin delete-lifecycle-module concurrency/TP-shutdown

$ asadmin delete-custom-resource concurrency/TP
This is how it looks like in admin console, where you can easily manage it:

To use this resource, just inject or look up in application components. The following is a test servlet that verifies that the resource can be obtained with either @Resource or regular lookup, and that multiple lookups return the same instance.
package test;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.annotation.*;
import javax.naming.*;

@javax.servlet.annotation.WebServlet(urlPatterns = "/*")
public class TestServlet extends HttpServlet {
@Resource(name="java:app/env/concurrency/TP", mappedName="concurrency/TP")
private test.ThreadPoolExecutor tp;

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
testLookup();
System.out.println("About to submit tasks to " + tp);
for(int i = 0; i < 30; i++) {
tp.execute(new MyRunnable());
}
}

private void testLookup() throws ServletException {
try {
for(int i = 0; i < 10; i++) {
ThreadPoolExecutor t = InitialContext.<ThreadPoolExecutor>doLookup("java:app/env/concurrency/TP");
System.out.println("ThreadPoolExecutor from lookup: " + t);
}
} catch (NamingException e) {
throw new ServletException(e);
}
}

private static class MyRunnable implements Runnable {
public void run() {
System.out.println("Executing task in " + Thread.currentThread());
}
}
}
When running the test webapp at http://localhost:8080/test/, many log messages like these will appear in server.log:
ThreadPoolExecutor from lookup: test.ThreadPoolExecutor@12e0a75a
Executing task in Thread[pool-29-thread-6,5,grizzly-kernel
In the above output, pool-29-thread-6 is the thread name, 5 (normal) is its priority, and grizzly-kernel is thread group name. When shutting down the server, the thread pool is purged and shutdown, thanks to the lifecycle module concurrency/TP-shutdown:
Server shutdown initiated
About to purge and shutdown test.ThreadPoolExecutor@12e0a75a, active thread count: 0
JMXStartupService: Stopped JMXConnectorServer: null
JMXStartupService and JMXConnectors have been shut down.
Shutdown procedure finished
This is just a simplistic implementation of thead pool resource in GlassFish. There is a potential class loader leak and may cause ClassCastException and/or OutOfMemoryError in large applications.

9 comments:

Anonymous said...

Hi,
Why not have some syntax highlighting?

Steve said...

Have to agree with Anon above - some syntax highlighting might be usefl?

erik56d said...

Eventually, I found here an actually working example on how to utilize thread pools in Glassfish.
Thank you!
Only problem was that I had to use the exact JNDI name, as given when defining the custom resource, when looking up the Executor, not prepending with "java:comp:env/".

javahowto said...

It's probably because you didn't declare a resource reference in your app to the custom resource created in the server. In my test code, the lookup part is not really needed since the resource reference is already injected into the servlet class with @Resource. This @Resource annotation also declares a resource ref in the current webapp which is looked up in testLookup() method.

Vee Eee Technologies said...

I have no words for this post. Thanks a lot for the share. Keep posting such a kind of post on your blog.

Frank Clements said...

Great write up! I was wondering though, would you be able to explain how exactly you came up with this solution?

As the old saying goes: "Give a man a fish and you feed him for a day. Teach a man to fish and you feed him for a lifetime."

Anonymous said...

That's quite a good idea. But is there no way of accessing the glassfish threadpool itself? This would let the application server handle resource management

javahowto said...

GlassFish built-in thread pools are all for internal use by web container, ejb container, deployment processor, resource adapters, etc. They are not accessible to applications, because they don't want applicatoins to contend for server resources. However, I agree there should be a supported way of creating thread pool resources just for application use, but I haven't seen one.

There are efforts in jcp trying to standardize it around worker manager and concurrency utils, but both are inactive now:

JSR 236: Concurrency Utilities for JavaTM EE

JSR 237: Work Manager for Application Servers

Anonymous said...


Greate post.
Probably the part related to the LifecycleListener is not working.

You should put your jar/classes on glassfish3/glassfish/lib in order get the correct glassfish context.