sebastiandaschner blog


Enhanced CDI contexts & bulkheads with MicroProfile Context Propagation

#microprofile #jakarta thursday, july 25, 2019

When using CDI with asynchronous execution methods, such as a ManagedExecutorService, it’s traditionally not possible to access all of CDI’s scopes that were active in the originating thread. MicroProfile Context Propagation enables to define and pass thread execution contexts to completion stages where our code can access various CDI contexts despite being executed asynchronously. Additionally, Context Propagation allows to create managed executor services, that can be injected and used inside our beans, for example to realize bulkheads.

Enhanced CDI contexts

Let’s create and use a request-scoped bean that is being used during the handling of a request. With plain CDI, we would not be able to access and lookup the bean within an asynchronous execution.

Have a look at the following code:

@ApplicationScoped
@Path("contexts/example")
public class ThreadContextExampleResource {

    @Inject
    ExampleStore exampleStore;

    @Inject
    ThreadContext threadContext;

    @Resource
    ManagedExecutorService mes;

    @Inject
    Notifier notifier;

    @PUT
    public void setExample(String example) {
        exampleStore.setExample(example);
        mes.execute(threadContext.contextualRunnable(notifier::notifyAbout));
    }
}
@RequestScoped
public class ExampleStore {

    private String example;

    public String getExample() {
        return example;
    }

    public void setExample(String example) {
        this.example = example;
    }
}
public class Notifier {

    @Inject
    ExampleStore exampleStore;

    public void notifyAbout() {
        System.out.println("New example: " + exampleStore.getExample());
    }
}

If a client PUTs some content to the contexts/example resource, the method will update the request-scoped ExampleStore bean and execute the notification asynchronously, using the ManagedExecutorService. In order to enable the asynchronous execution to lookup the request-scoped store, we are using the ThreadContext to wrap the runnable with a context captured from the originating thread. This makes sure that the executed runnable can use the corresponding context.

We have to configure and produce a ThreadContext depending on which type of contexts (e.g. CDI, transaction, security) we want to propagate:

public class ThreadContextProducer {

    @Produces
    ThreadContext threadContext() {
        return ThreadContext.builder()
                .propagated(ThreadContext.ALL_REMAINING)
                .build();
    }
}

This example will propagate all context types to the wrapped execution. Our bean then injects and uses the produced ThreadContext.

 

Defining bulkheads using executors

MicroProfile Context Propagation allows to create and configure ManagedExecutors, a container-managed executor service similar to ManagedExecutorService. We can create a ManagedExecutor programmatically, set constraints on the allowed concurrency, and define a context propagation, as well.

By using dedicated executors for specific functionality, we can implement the bulkhead pattern, similar to using MicroProfile Fault Tolerance, or Porcupine.

Let’s define the following asynchronous JAX-RS resources:

@ApplicationScoped
@Path("bulkheads")
public class BulkheadExampleResource {

    @Inject
    ExampleStore exampleStore;

    @Inject
    Notifier notifier;

    @Inject
    ManagedExecutor writeExecutor;

    @Inject
    ManagedExecutor readExecutor;

    @GET
    public CompletionStage<String> example() {
        return readExecutor.supplyAsync(exampleStore::getExample);
    }

    @PUT
    public CompletionStage<Void> setExample(String example) {
        return writeExecutor.runAsync(() -> {
            exampleStore.setExample(example);
            writeExecutor.execute(notifier::notifyAbout);
        });
    }

}

We’re injecting two dedicated executors, that are used to run the corresponding functionalities. The executors are created using a producer:

public class ManagedExecutorProducer {

    @Produces
    ManagedExecutor managedExecutor() {
        return ManagedExecutor.builder()
                .propagated(ThreadContext.CDI, ThreadContext.APPLICATION)
                .maxAsync(4)
                .maxQueued(4)
                .build();
    }

    public void disposeManagedExecutor(@Disposes ManagedExecutor managedExecutor) {
        managedExecutor.shutdownNow();
    }
}

Our executors will have upper bounds of four concurrently executed completion stages, and four tasks in the queue. The contexts of the CDI and application context types will be propagated to the executing threads.

When injecting the executors, be aware of the scope of the injection point; here we’re using an application-scoped resource, otherwise we might create more than two executors, which would defeat the purpose of the bulkhead pattern. Since we’re using CDI it’s of course possible to define additional qualifies if the created executors should be configured differently.

 

You can try out MicroProfile Context Propagation for example using the latest builds of Open Liberty. I’ve published an example repository on GitHub.

When we’re running our applications on Open Liberty, MicroProfile Context Propagation executors are backed by the automatically-tuned global thread pool. You can have a look at the default thread pool metrics provided by Liberty, as shown here.

 

Further resources

 

Found the post useful? Subscribe to my newsletter for more free content, tips and tricks on IT & Java: