Skip to content

Latest commit

 

History

History
175 lines (134 loc) · 9.67 KB

File metadata and controls

175 lines (134 loc) · 9.67 KB

MicroProfile Concurrency Specification

Introduction

MicroProfile Concurrency introduces APIs for obtaining instances of CompletableFuture that are backed by managed threads (threads that are managed by the container), with the ability to capture context from the thread that creates the CompletableFuture and apply it when running the associated action.

Motivation

When using a reactive model with dependent stages which execute upon completion of prior stages, the context under which dependent stages execute is unpredictable. Dependent stages might run with the context of a thread that awaits completion, or the context of a previous stage that completed and triggered the dependent stage, or with no/undefined context at all. Existing solutions for transferring thread context, such as the EE Concurrency Utilities ContextService, are difficult to use and require a lot of boilerplate code. This specification makes it possible for thread context propagation to easily be done in a type-safe way, keeping boilerplate code to a minimum, as well as allowing for thread context propagation to be done automatically when using a CompletableFuture.

It is also important that a CompletableFuture and its dependent stages, and dependent stages created by those stages, and so on, all run on threads that are known by the container and properly managed by it. This specification makes it possible to do so by providing an API by which the user obtains managed CompletableFuture instances from a managed executor.

Goals

  • Proper management of CompletableFuture threads by the container.

  • Mechanism for thread context propagation to CompletableFuture actions that reduces the need for boilerplate code.

  • Full compatibility with EE Concurrency spec, such that proposed interfaces can eventually be seamlessly merged into EE Concurrency.

Solution

This specification introduces two interfaces that contain methods that we hope will eventually be added to Jakarta EE Concurrency.

The interface, org.eclipse.microprofile.concurrent.ManagedExecutor, provides methods for obtaining managed instances of CompletableFuture which are backed by the managed executor as the default asynchronous execution facility and the default mechanism of defining thread context propagation. Similar to EE Concurrency’s ManagedExecutorService, the MicroProfile ManagedExecutor also implements the Java SE java.util.concurrent.ExecutorService interface, using managed threads when asynchronous invocation is required. It is intended that ManagedExecutor methods will one day be added to ManagedExecutorService, and for a single implementation to be capable of simultaneously implementing both interfaces, both currently as well as after adoption into Jakarta EE.

A second interface, org.eclipse.microprofile.concurrent.ThreadContext, provides methods for individually contextualizing dependent stage actions. This gives the user finer-grained control over the capture and propagation of thread context. It is intended that ThreadContext methods will one day be added to EE Concurrency’s ContextService and for a single implementation to be capable of simultaneously implementing both interfaces, both currently as well as after adoption into Jakarta EE.

Builders

Instances of ManagedExecutor and ThreadContext can be constructed via builders with fluent API, for example,

    ManagedExecutor executor = ManagedExecutor.builder()
        .propagated(ThreadContext.APPLICATION)
        .maxAsync(5)
        .build();

    ThreadContext threadContext = ThreadContext.builder()
        .propagated(ThreadContext.APPLICATION, ThreadContext.CDI)
        .cleared(ThreadContext.ALL_REMAINING)
        .build();

Applications should shut down instances of ManagedExecutor that they build after they are no longer needed. The shutdown request serves as a signal notifying the container that resources can be safely cleaned up.

Injection

Instances of ManagedExecutor and ThreadContext can be injected into CDI beans via the @Inject annotation. Injection points with the @NamedInstance qualifier have a name that is explicitly declared by the qualifier. Injection points that have no CDI qualifier on them have a unique name which is deduced from the injection point itself. Container provides, per unique name, an instance of either ManagedExecutor and/or ThreadContext unless the application specifies a CDI producer with @NamedInstance qualifier, given unique name and respective type. for example:

    @Inject ManagedExecutor executor;
    @Inject ThreadContext threadContext;
    ...
    CompletableFuture<Integer> stage = executor
        .supplyAsync(supplier1)
        .thenApplyAsync(function1)
        .thenApply(function2);
    ...
    unmanagedCompletionStage.thenApply(threadContext.contextualFunction(function3));

In the absence of other qualifiers and annotations, the container creates and injects a new default instance of ManagedExecutor or ThreadContext per injection point, as shown above. The configuration of these instances is equivalent to the default values of @ManagedExecutorConfig and @ThreadContextConfig.

Injection of Configured Instances

The @ManagedExecutorConfig and @ThreadContextConfig annotations provide the ability to configure the injection points with desired capabilities. With just the config annotation in place, every injection point is assigned a new instance.

    @Inject
    @ManagedExecutorConfig(maxAsync = 5, propagated = ThreadContext.SECURITY)
    ManagedExecutor configuredExecutor;
    ...
    CompletableFuture<Long> stage = configuredExecutor
        .newIncompleteFuture()
        .thenApply(function)
        .thenAccept(consumer);
    stage.completeAsync(supplier);

Sharing Configured Instances

Configured instances of ManagedExecutor and ThreadContext are shared based on their unique name, which is assigned by the CDI qualifier, @NamedInstance. When one injection point is annotated with both a configuration and a @NamedInstance qualifier, the application can inject the same instance elsewhere by annotating other injection points with just the matching qualifier. In the example below, the executor1 injection point defines a configuration and a name myExec for a configured ManagerExecutor instance. The other injection points, executor2 and executor3, share the same ManagedExecutor instance by using the @NamedInstance qualifier with the name myExec.

    @Inject
    @NamedInstance("myExec")
    @ManagedExecutorConfig(propagated = { ThreadContext.SECURITY, ThreadContext.APPLICATION })
    ManagedExecutor executor1;
    ... // in some other bean
    @Inject
    void setCompletableFuture(@NamedInstance("myExec") ManagedExecutor executor2) {
        completableFuture = executor2.newIncompleteFuture();
    }
    ... // in yet another bean
    @Inject
    @NamedInstance("myExec")
    ManagedExecutor executor3;

Integration with MicroProfile Config

This specification defines a convention for defining properties in MicroProfile Config that override configuration attributes of ManagedExecutor and ThreadContext instances that are produced by the container. The convention for the property name is the fully qualified name of the injection point, with the . character as delimiter.

The following example shows one injection point for ManagedExecutor, which is the first parameter of the setCompletableFuture method, and another injection point for ThreadContext, which is the appContextPropagator field.

package org.eclipse.microprofile.example;

import org.eclipse.microprofile.concurrent.ManagedExecutor;
import org.eclipse.microprofile.concurrent.ThreadContext;
import org.eclipse.microprofile.concurrent.ThreadContextConfig;
import java.util.concurrent.CompletableFuture;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;

@ApplicationScoped
public class ExampleBean {
    CompletableFuture<Long> completableFuture;

    @Inject
    void setCompletableFuture(ManagedExecutor exec) {
        completableFuture = exec.newIncompleteFuture();
    }

    @Inject @ThreadContextConfig(propagated = ThreadContext.APPLICATION,
                                 cleared = ThreadContext.TRANSACTION,
                                 unchanged = ThreadContext.ALL_REMAINING)
    ThreadContext appContextPropagator;
}

The following MicroProfile config properties could be used to override specific configuration attributes of these instances,

org.eclipse.microprofile.example.ExampleBean.setCompletableFuture.1.maxAsync=5
org.eclipse.microprofile.example.ExampleBean.setCompletableFuture.1.maxQueued=20
org.eclipse.microprofile.example.ExampleBean.appContextPropagator.cleared=Security,Transaction

Thread Context Provider SPI

External providers of thread context can implement an SPI standardized by the MicroProfile Concurrency specification that allows capture/clear/propagate/restore operations to be plugged in for third party implementations of context.