0

I'm implementing a JavaEE8 application using CDI and running on an Open Liberty (v20.0.0.4). The application has a event-triggered job, which runs some code in an separate thread using the ThreadPoolExecutor like this:

@Singleton
public class MyJobExecutorService {

    @PostConstruct
    public void init() {
        thredPoolExecutor = new ThreadPoolExecutor(1, 1,
                0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
    }

    public void runJob(MyConfigs configs) {
        thredPoolExecutor.submit(() -> new MyJobRunnable(configs).run());
    }
}

The job gets data from the underlying sql database using an EntityManager, which is injected in the data access class and produced like following. My querys are written using querydsl (which should not be relevant).

public class EntityManagerProducer {

    @PersistenceContext(unitName = "my-unit")
    private EntityManager entityManager;

    @Produces
    @Dependent
    public EntityManager getEntityManager() {
        return entityManager;
    }
}

My persistence.xml looks like this:

<persistence ...>
    <persistence-unit name=my-unit">
        <jta-data-source>jdbc/datasource</jta-data-source>
    </persistence-unit>
</persistence>

I have no issues accessing the database from the main thred, but the job throws a NullPointerException with the following stacktrace (and no further information):

com.ibm.ws.jpa.management.JPATxEntityManager.getEMInvocationInfo(JPATxEntityManager.java:213)
com.ibm.ws.jpa.management.JPATxEntityManager.getEMInvocationInfo(JPATxEntityManager.java:164)
com.ibm.ws.jpa.management.JPAEntityManager.getDelegate(JPAEntityManager.java:402)
com.querydsl.jpa.impl.JPAProvider.getTemplates(JPAProvider.java:61)
com.querydsl.jpa.impl.JPAQuery.<init>(JPAQuery.java:48)
com.querydsl.jpa.impl.JPAQueryFactory.query(JPAQueryFactory.java:138)
com.querydsl.jpa.impl.JPAQueryFactory.select(JPAQueryFactory.java:81)
com.querydsl.jpa.impl.JPAQueryFactory.selectFrom(JPAQueryFactory.java:111)
my.application.MyRepository.getAll(DataAccess.java:67)
sun.reflect.GeneratedMethodAccessor1888.invoke(UnknownSource)
java.lang.reflect.Method.invoke(Method.java:498)
org.jboss.weld.bean.proxy.AbstractBeanInstance.invoke(AbstractBeanInstance.java:38)
org.jboss.weld.bean.proxy.ProxyMethodHandler.invoke(ProxyMethodHandler.java:106)
my.application.MyRepository$Repository$Serializable$925348889$Proxy$_$$_WeldClientProxy.getAll(UnknownSource)
sun.reflect.GeneratedMethodAccessor1887.invoke(UnknownSource)
java.lang.reflect.Method.invoke(Method.java:498)
org.jboss.weld.bean.proxy.AbstractBeanInstance.invoke(AbstractBeanInstance.java:38)
org.jboss.weld.bean.proxy.ProxyMethodHandler.invoke(ProxyMethodHandler.java:106)
my.application.DataAccess$587668909$Proxy$_$$_WeldClientProxy.getAllData(UnknownSource)
my.application.job.MyDefaultJob.runJob(MyDefaultJob.java:50)
sun.reflect.GeneratedMethodAccessor1886.invoke(UnknownSource)
java.lang.reflect.Method.invoke(Method.java:498)
org.jboss.weld.bean.proxy.AbstractBeanInstance.invoke(AbstractBeanInstance.java:38)
org.jboss.weld.bean.proxy.ProxyMethodHandler.invoke(ProxyMethodHandler.java:106)
my.application.job.MyJob$588111896$Proxy$_$$_WeldClientProxy.runJob(UnknownSource)
my.application.job.MyJobExecutorService$MyRunnable.run(MyJobExecutorService.java:59)
my.application.job.MyJobExecutorService.lambda$runJob$0(MyJobExecutorService.java:36)
my.application.job.MyJobExecutorService$$Lambda$280/00000000A8128A20.run(UnknownSource)
java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
java.util.concurrent.FutureTask.run(FutureTask.java:266)
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
java.lang.Thread.run(Thread.java:823)

Why am I getting this exception and how can I fix this? Activating the jpa an concurrency features in the server.xml of the application server didnt help. Thanks a lot.

cloudy_rowdy
  • 77
  • 1
  • 13
  • 1
    My guess is that this is because of 'unmanaged' threads, which dont have access to jee context. Try to replace your `ThreadPoolExecutor` with `ManagedExecutorService`. Check this page for more details https://www.ibm.com/docs/en/was-liberty/core?topic=manually-configuring-managed-executors – Gas Jan 18 '22 at 13:44
  • I used the ThreadPoolExecutor because I needed access to the queue in the executor to check some stuff. I tried it with the ManagedExecutorService and it works now. So I need another way to access the queue, because the ManagedExecutorService does not provide such methods. But anyways, you solved my issue, thanks a lot :) Can you briefly explain, why the ManagedExecutorService solves my issue? – cloudy_rowdy Jan 18 '22 at 14:08
  • ManagedExecutor solved your issue as it has access to JEE context, so in your case EntityManager. Alternatively you could inject application managed JPA via `PersistenceUnit` instead of `PersistenceContext` (which is container managed). – Gas Jan 18 '22 at 15:35

1 Answers1

0

Enabling the concurrent-1.0 feature alone doesn't do anything unless you are using the managed resources that it provides which capture the context of the application component (such as its java:comp name space and so forth) and makes it available when running the tasks that are submitted to it.

If you must use a ThreadPoolExecutor in order to manipulate its queue in some way beyond enforcing concurrency constraints (ManagedExecutorService can impose concurrency constraints via a configurable concurrencyPolicy), the simplest way to continue using a ThreadPoolExecutor is by supplying it with a ManagedThreadFactory,

    @PostConstruct
    public void init() {
        ManagedThreadFactory threadFactory = InitialContext.doLookup(
            "java:comp/DefaultManagedThreadFactory");
        thredPoolExecutor = new ThreadPoolExecutor(1, 1,
                0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(),
                threadFactory);
    }

ManagedThreadFactory captures the context that is present on the thread from which it is initially looked up. You'll need to decide if there is a better place for it than your init() method based on what context you want it to provide to your ThreadPoolExecutor tasks.

You should also be aware that any use of ThreadPoolExecutor (even in combination with a ManagedThreadFactory or ContextService) bypasses use of the Liberty global thread pool.

njr
  • 3,399
  • 9
  • 7