Fault Tolerance 5.6.0 and 6.1.0

Today, we announce the release of SmallRye Fault Tolerance 5.6.0. This release includes several new features, one important deprecation and one important bug fix.

Rate limiting

SmallRye Fault Tolerance adds a new fault tolerance strategy: @RateLimit.

Rate limit enforces a maximum number of permitted invocations in a time window of some length. For example, with a rate limit, one can make sure that a method may only be called 50 times per minute. Invocations that would exceed the limit are rejected with an exception of type RateLimitException.

Rate limit is superficially similar to a bulkhead (concurrency limit), but is in fact quite different. Bulkhead limits the number of executions happening concurrently at any point in time. Rate limit limits the number of executions in a time window of some length, without considering concurrency.

For example:

@RateLimit(value = 50, window = 1, windowUnit = ChronoUnit.MINUTES)
public void doSomething() {
    ...
}

This makes sure that doSomething may only be called 50 times per minute.

There are 3 kinds of rate limiting that can be configured using @RateLimit(type = ...). They correspond to the most common rate limiting algorithms:

  • RateLimitType.FIXED: a fixed window kind of rate limiting; this is the default

  • RateLimitType.ROLLING: a sliding log kind of rate limiting

  • RateLimitType.SMOOTH: a token bucket kind of rate limiting (could also be called rate shaping or rate smoothing)

Additionally, it is possible to define minimum spacing between invocations. For example, with minimum spacing of 1 second, if a second invocation happens 500 millis after the first, it is rejected even if the limit wouldn’t be exceeded yet.

Lifecycle

Since rate limit is a stateful fault tolerance strategy, it is important to consider its lifecycle.

Currently, rate limit is a global singleton, just like circuit breaker or bulkhead. SmallRye Fault Tolerance doesn’t allow changing it at the moment, so e.g. rate limit per user is currently impossible to implement. This is something we plan to address in the future, so feedback is welcome!

Other annotations

@RateLimit can be used together with all other fault tolerance annotations, as usual. If a method would hypothetically declare all fault tolerance annotations, the fault tolerance strategies would be nested like this:

Fallback(
    Retry(
        CircuitBreaker(
            RateLimit(
                Timeout(
                    Bulkhead(
                        ... the guarded method ...
                    )
                )
            )
        )
    )
)

A lot more information about rate limiting can be found in the documentation.

New annotation for asynchronous non-blocking methods

SmallRye Fault Tolerance used to discourage @Asynchronous and recommend @Blocking and @NonBlocking instead. This is problematic, because the @Blocking and @NonBlocking annotations are used by various containers to affect how the container invokes the method. If a container executes a @Blocking method on another thread, and that method applies some fault tolerance strategy, then SmallRye Fault Tolerance would execute it on yet another thread. This is hardly optimal.

For that reason, starting with SmallRye Fault Tolerance 5.6.0 / 6.1.0, the usage of @Blocking and @NonBlocking for fault tolerance purposes is deprecated.

The recommendation is to use @Asynchronous for methods that require offloading execution to another thread. For asynchronous methods that do not require thread offload and return CompletionStage (or some other async type such as Uni), the recommendation is:

  • in non-compatible mode, no additional annotation is necessary, it is detected automatically;

  • in fully compatible mode, use @AsynchronousNonBlocking.

@AsynchronousNonBlocking is a new annotation introduced by SmallRye Fault Tolerance in this version. The relevant section in the documentation was thoroughly revised and contains all the details.

Compatibility

SmallRye Fault Tolerance will recognize and support the @Blocking and @NonBlocking annotations for the entire lifecycle of 5.x and 6.x. Support for these annotations will be removed in SmallRye Fault Tolerance 7.0.0.

This does not imply that these annotations should be avoided for other purposes. This change only affects SmallRye Fault Tolerance.

Fallback methods with exception parameter

In the non-compatible mode, SmallRye Fault Tolerance supports access to the causing exception in a @Fallback method.

A fallback method, as defind by the MicroProfile Fault Tolerance specification, must have the same parameters as the guarded method. SmallRye Fault Tolerance permits defining one additional parameter, at the end of the parameter list, which must be of an exception type. If such parameter is defined, the exception that caused the fallback will be supplied in it.

For example:

@ApplicationScoped
public class MyService {
    @Fallback(fallbackMethod = "fallback")
    public String doSomething(String param) {
        ...
    }

    public String fallback(String param, IllegalArgumentException cause) {
        ...
    }
}

It is possible to declare multiple overloads of the fallback method, each having different type of the exception parameter. It is also possible to declare fallback method(s) with an exception parameter together with a fallback method without an exception parameter.

As usual, see the documentation for more information.

Circuit breaker state transitions

The circuit breaker implementation in SmallRye Fault Tolerance used to perform state transitions only during invocations (that is, synchronously). This is easy to implement and doesn’t affect correctness of the circuit breaker algorithm, so there’s nothing wrong with it.

However, SmallRye Fault Tolerance also allows external observation of the circuit breaker state. With the existing implementation of state transitions only during invocations, such external observations would be correct when moving from closed to open and from half-open to closed (because these transitions are defined to happen as a result of an invocation), but would not necessarily be correct when moving from open to half-open (which is defined to happen after certain amount of time, regardless of invocations).

For example, if the circuit breaker is configured to transition from open to half-open after 5 seconds, but there are no invocations for 10 seconds, then external observers would see the circuit breaker staying open for twice the time it should be.

With this release, the circuit breaker will perform transitions from open to half-open asynchronously, using a timer. This means that external observers of the circuit breaker state will always be correct.

SmallRye Fault Tolerance 6.1.0

Coming together with SmallRye Fault Tolerance 5.6.0 is the release of SmallRye Fault Tolerance 6.1.0.

SmallRye Fault Tolerance 6.1.0 is the 6.x release corresponding to 5.6.0. That is, it has all the features of 5.6.0, but requires Java 11 at minimum and uses the Jakarta EE 9 and MicroProfile 5.0 APIs. See the previous announcement for more details about the correspondence between 5.x and 6.x.