RequestScoped with bean re-use

Viewed 1233

I have a class/bean that manages an object (in this example the EngineManager contains an Engine object). The Engine object cannot be used concurrently and its initialization is a bit time consuming. However it is possible to create multiple instances of the EngineManager and hence multiple Engine instances.

public class EngineManager
{
    private Engine engine;

    @PostConstruct
    public void init()
    {
        this.engine = // ... perform costly initialization
    }

    public void doSomethingWithEngine()
    {
        // ...
    }
}

I'm trying to figure out which CDI scope to use for the class, that manages this object.

  • I don't want to make the class a singleton, since I can create multiple instances of it and a singleton would be a bottleneck.
  • I cannot use @ApplicationScoped due to concurrency issues.
  • I don't want to use RequestScoped, because to my understanding this creates a new instance for every single request and the costly initialization of the Engine object would be a lot of overhead.

So my question is: Is there a (CDI) way to

  1. make the access to the EngineManager class thread-safe and
  2. have multiple instances of the EngineManager class, that are reused?
2 Answers

In short: To my knowledge, there is no way to solve this strictly within CDI without extra effort. Here are some generic thoughts:

This problem is similar to that of the DB connection pool. One way to solve it is with a pool of Engine instances, from which the EngineManager(s) pick.

Elaborating a bit, and if you use an engine pool, the EngineManager can be @ApplicationScoped, as long as the pool guarantees that each thread gets a different Engine.

An interesting aspect of this is how do you deal with unavailability of Engine instances. Throwing an exception is the simplest answer, but might not be appropriate for you use case. Blocking the current thread (probably with a timeout) until an Engine is available is another sub-optimal solution because it will not scale well under traffic.

If your environment allows, you may want to consider an asynchronous solution, in combination with the pool. An ExecutorService (see ManagedExecutorService in JEE environments) where you submit tasks; JMS or other queuing mechanism might be more complex to setup (again depending on your environment) but can offer reliability in the form of message persistence (if the server crashes after you submit your work but before retrieving the result, it can resume and complete the work when it comes back online). Going full async requires more effort, but might be more appropriate if your specific use case justifies it.

Reactions to the comments:

  • EJBs are the natural way for such use cases in traditional JEE applications. You will be using the facilities provided by the application server. (My instinct is to stay away of EJBs in the present day... just saying)
  • You are on Quarkus (good IMO). If you go for a queue, you will have to setup a different system - you can judge if it is worth it. Quarkus supports asynchronous execution in many ways (and you may even want to try the reactive streams solutions).
  • I was not aware of the omniservices library mentioned. It may suit your needs, but requires conversion to a Quarkus extension, as Quarkus does not support CDI portable extensions at this time, sadly.

Nikos answer is good, so this one is just to expand it a bit. There is indeed no ready-to-use solution for this problem. From what I understood, the main issue here is the Engine object and it's sharing. You want to be able to hold n instances and distribute them between m EngineManager instances.

Note that if you use @Inject to get Engine to EngineManager the engine is bound to the manager for the lifecycle of the manager. So if you want to swap it dynamically (e.g. one manager using different engines for different invocations), then you would have to also use dynamic resolution (Instance<T>). Based on this your EngineManager can be either dependent or application scoped.

I can think of two ways to go about the Engine instance being shares and having multiple instances.

  1. Create a bean that holds a @Dependent scope producer for Engine. Now this producer gets called for every injection of Engine and you can control what it returns. The bean can hold a collection of Engines and sometimes it gives you an existing one if they are free, sometimes it could create new one. Thread safety is up to you though!

  2. Define your own custom scope that will fit your needs. This requires some expertise and usage of Quarkus specific APIs as in CDI you would normally use extensions but you cannot do that in Quarkus. For instance in Weld SE, you have @ThreadScoped which might be something that you could re-implement as custom scope in Quarkus and use in case you want an Engine to be on a per-thread basis. However, custom scoped can really do next to anything, this is just an example.

Related