I have a Singleton Session Bean executing background tasks:
@Singleton
@ConcurrencyManagement(ConcurrencyManagementType.BEAN)
@TransactionManagement(TransactionManagementType.BEAN)
@Startup
public class TaskQueue {
private static final Logger LOGGER = Logger.getLogger("TaskQueue");
@Resource
private SessionContext sessionContext;
private final ArrayList<Runnable> tasks = new ArrayList<Runnable>();
private boolean running = false;
@PostConstruct
public void postConstruct() {
LOGGER.info("postConstruct");
running = true;
sessionContext.getBusinessObject(TaskQueue.class).taskLoop();
}
@PreDestroy
public void preDestroy() {
LOGGER.info("preDestroy");
running = false;
synchronized (tasks) {
tasks.notifyAll();
}
}
public void addTask(Runnable r) {
synchronized (tasks) {
tasks.add(r);
tasks.notifyAll();
}
}
@Asynchronous
public void taskLoop() {
while (running) {
Runnable task;
synchronized (tasks) {
LOGGER.info("Fetching next task...");
if (tasks.isEmpty()) {
try {
LOGGER.info("Task queue is empty. Waiting...");
tasks.wait();
LOGGER.info("Resumed");
continue;
} catch (InterruptedException e) {
break;
}
}
task = tasks.remove(0);
}
LOGGER.info("Executing task...");
task.run();
}
running = false;
LOGGER.info("Task queue exited");
}
}
When I tried to stop the module, undeploy the module, or stop the server, the preDestroy()
method did not get called, and the stopping/undeployment process won't proceed. The only way to stop the server is to kill the Java process.
I am using Jboss EAP 6.0.
What's going wrong with my code? How to fix it, or what's the alternative way to do background task queue processing with EJB 3.1?
Firstly, managing your own threads is forbidden by the EE spec. You can do it but you should be aware that you are violating the spec and understand all the inherent implications. Instead you should look into leveraging the Managed Executor service [1].
With that said what I suspect is going on here is that your taskLoop is locking access to the rest of your Singletons (including pre-destroy) methods. By default all methods on a @Singleton are @Lock LockType.Write. Since your are already manually synchronizing you should try annotating your @Singleton class with @Lock(LockType.Read) [2]
[1] https://docs.oracle.com/javaee/7/api/javax/enterprise/concurrent/ManagedExecutorService.html
[2] https://docs.oracle.com/javaee/6/api/javax/ejb/LockType.html