I think my question has not been asked in this way before:
I want to fill the Cache of my Project on Startup. The documentation on the Quarkus Website tells me, that one way to do this is to add the "@Observes StartupEvent e" construct into the method I am trying to run. When filling the cache, I am pulling data from a REST API of which I am injecting the corresponding client into the class where my StartupMethod resides (so that I can store the result in its respective field)
Because this data-fetching takes very long, I want to set it off onto another thread which is created shortly after the StartupEvent method has been triggered. This is what my construct looks like:
public class CacheClass {
private List<SomeObject> cacheData;
@Inject
SomeRestClient someRestClient;
public void init(@Observes StartupEvent e) {
//Open a new Thread for the cache fill method
Thread thread = new Thread(() -> fetchDataForCache());
thread.start();
//After that, open more threads which shall fill different Caches
}
public void fetchDataForCache() {
List<SomeObject> tmpData = someRestClient.fetchSomeData();
//Do some validation and write the result into "dataCache"
}
//Getter, Setter and other stuff
}
The thread that is supposed to be opened will successfully do so, however, as soon as the method call to the REST Client is made, I get a ContextNotActiveException which looks like this:
Exception in thread "Thread-68" javax.enterprise.context.ContextNotActiveException
at io.quarkus.arc.impl.ClientProxies.getDelegate(ClientProxies.java:46)
at someRestPackage.SomeRestClient_ClientProxy.arc$delegate(Unknown Source)
at someRestPackage.SomeRestClient_ClientProxy.findLatestEntry(Unknown Source)
at somePackage.CacheClass(CacheClass.java:15)
at somePackage.CacheClass_Subclass.fetchDataForCache$$superforward1(Unknown Source)
at somePackage.CacheClass_Subclass$$function$$3.apply(Unknown Source)
at io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:54)
at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.proceed(InvocationInterceptor.java:62)
at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.monitor(InvocationInterceptor.java:51)
at io.quarkus.arc.runtime.devconsole.InvocationInterceptor_Bean.intercept(Unknown Source)
at io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:41)
at io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:41)
at io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:32)
at somePackage.CacheClass_Subclass.fetchDataForCache(Unknown Source)
at somePackage.CacheClass$1.run(CacheClass.java:11)
at java.base/java.lang.Thread.run(Thread.java:829)
However, when removing the Thread part and just calling "fetchDataForCache()" on the main thread, the method gets executed without any problems, so it must have to do something with the multithreading
Now maybe I am missing something really obvious and I would greatly appreciate someone explain to me why this happens and how to fix it or, maybe to give me some alternatives to how an asynchronous StartupEvent (with the needed Context) can be accomplished.
CodePudding user response:
Your newly created thread has no (CDI) context active as it tries to access CDI bean (someRestClient). You need to propagate the context to the new thread, when you start it.
Use following extension to do it for you
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-context-propagation</artifactId>
</dependency>
Afterwards you can create ManagedExecutor provided by this extension
ManagedExecutor executor = ManagedExecutor.builder()
.maxAsync(1)
.propagated(ThreadContext.CDI, ThreadContext.APPLICATION)
.build();
and let it execute your async work. (Don't forget to close it, to release any resources when it isn't needed anymore)
More information https://github.com/eclipse/microprofile-context-propagation/, https://quarkus.io/guides/context-propagation