back to all blogsSee all blog posts

MicroProfile Context Propagation in Open Liberty development builds

image of author
Nathan Rauh on Mar 1, 2019

Completion stages and completable futures (introduced in Java SE 8) enable you to chain together pipelines of dependent actions with the ability to run sequentially or in parallel, depending on how the API is used. Execution of a dependent stage is triggered by the completion of the stage (or stages) upon which it depends. While this capability provided by completion stages can be extremely useful in enabling applications to implement a model where they react to events as they happen, the built-in implementation (completable future) neglects several important areas: consistency and reliability of thread context and defaulting asynchronous dependent stage actions to run on the Liberty global thread pool. MicroProfile Context Propagation introduces the ManagedExecutor and ThreadContext API to address these problems. An implementation of the MicroProfile Context Propagation specification is now available in the Open Liberty development builds.

Managed executors in MicroProfile Context Propagation allow you to use completion stages that run with predictable thread context regardless of which thread the action ends up running on. Without this feature, thread context of a completion stage action depends on whether the action runs on the requesting thread, the thread of a stage upon which it depends, a thread that is requesting the result, or a thread that forcibly completes the prior stage. There is no guarantee under which context the action will run. However, with MicroProfile Context Propagation, the thread context is completely deterministic because context is always captured from the thread that creates the completion stage and applied when running the action.

Alternatively, you can configure to clear certain context types rather than capturing context. For example, clearing the security context means that no user is associated with the thread while the completion stage action runs. When a completion stage is created by a managed executor, the managed executor remains associated with the completion stage and determines thread context propagation and for dependent stages that are requested to run asynchronously without designating a specific executor. The managed executor remains associated with each dependent stage created, and each dependent stage created from those, and so forth, allowing for predictable thread context propagation at every stage in the pipeline.

Here is an example of a managed executor that is used to propagate the application’s namespace to an asynchronous action,

CompletableFuture<Integer> stage = executor.supplyAsync(supplier1)
    .thenApply(function1)
    .thenApply(function2)
    .thenApply(i -> {
        try {
            DataSource ds = InitialContext.doLookup("java:module/env/jdbc/ds1");
            ...
            return result;
        } catch (Exception x) {
           throw new CompletionExeption(x);
        }
    });

Managed executors in MicroProfile Context Propagation are fully compatible with ManagedExecutorService in Java EE Concurrency, leaving open the possibility that the programming models could one day be merged. Notably, the org.eclipse.microprofile.concurrent.ManagedExecutor interface inherits from java.util.concurrent.ExecutorService, and thus allows the same execute/submit/invoke operations as ManagedExecutorService.

MicroProfile Context Propagation provides the org.eclipse.microprofile.concurrent.ThreadContext interface to facilitate the use of unmanaged completion stages. When you have a completion stage that isn’t created by a managed executor, it can still run with predictable thread context if you pre-contextualize its action with the corresponding method of MicroProfile Context Propagation ThreadContext. For example,

CompletableFuture<Long> stage = CompletableFuture.supplyAsync(supplier1)
    .thenApplyAsync(function1)
    .thenApply(threadContext.contextualFunction(function2));

How to obtain ManagedExecutor and ThreadContext instances

There are a variety of ways to obtain instances of ManagedExecutor and ThreadContext:

  • MicroProfile Context Propagation offers a fluent builder pattern for programmatic usage:

    ManagedExecutor executor = ManagedExecutor.builder()
        .maxAsync(10)
        .propagated(ThreadContext.APPLICATION, ThreadContext.SECURITY)
        .cleared(ThreadContext.ALL_REMAINING)
        .build();

    Applications should take care to shut down executor instances that they build, once the executor instance is no longer needed.

  • If you are using Java EE Concurrency, you can cast your existing ManagedExecutorService to ManagedExecutor.

  • MicroProfile Context Propagation also lets you configure and inject instances via CDI (requires the cdi-2.0 feature or higher).

    Example usage in a CDI bean:

    // CDI qualifier which is used to identify the executor instance
    @Qualifier
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER })
    public @interface AppContext {}
    
    // Example producer field, defined in a CDI bean,
    @Produces @ApplicationScoped @AppContext
    ManagedExecutor appContextExecutor = ManagedExecutor.builder()
        .propagated(ThreadContext.APPLICATION)
        .build();
    
    // Example disposer method, also defined in the CDI bean,
    void disposeExecutor(@Disposes @AppContext exec) {
        exec.shutdownNow();
    }
    
    // Example injection point, defined in a CDI bean,
    @Inject @AppContext
    ManagedExecutor executor;
    
    ...
    
    CompletableFuture<Integer> stage = executor
        .supplyAsync(supplier1)
        .thenApply(function1)
        .thenApplyAsync(value -> {
            try {
                // access resource reference in application's java:comp namespace,
                DataSource ds = InitialContext.doLookup("java:comp/env/jdbc/ds1");
                ...
                return result;
            } catch (Exception x) {
                throw new CompletionException(x);
            }
        });

MicroProfile Context Propagation builds upon Java SE CompletableFuture and builds out the necessary infrastructure around it, empowering you to build robust applications that react to events as they happen, under a dependable thread context and backed by the performance of Liberty threading.