reactor-core v3.4.0-M2 Release Notes

Release Date: 2020-08-11 // over 3 years ago
  • ๐Ÿš€ Reactor-Core 3.4.0.-M2 is part of 2020.0.0-M2 Release Train (codename Europium).

    This second milestone brings several important changes:

    • further evolution of the way processors and sinks are obtained and used (following up on the changes initiated in M1)
    • ๐Ÿ‘Œ improvements of the Context related APIs
    • ๐Ÿ‘Œ improvements on the Flux and Mono metrics

    ๐Ÿš€ This release note focuses on M2-specific changes, but M2 also contains all the changes released in 3.2.19.RELEASE, 3.3.8.RELEASE and 3.3.9.RELEASE.

    โšก๏ธ โš ๏ธ Update considerations and deprecations

    โšก๏ธ Metrics update considerations

    ๐Ÿท The name() operator is no longer used in metrics as a tag but as a prefix for the meter names (#1928):

    This allows to use custom tags via the tag() operator, while ensuring unique enough meter names that all bear the same set of tags

    The [name].flow.duration meter has a status tag that now differentiate between completed and completedEmpty (#2313)

    โšก๏ธ This allows to detect eg. empty Monos, but code relying on the completed status should be updated to also take completedEmpty into account.

    The [name].subscribed and [name].requested meters have had their unit dropped (#1807):

    ๐Ÿ›  The unit is part of the name in some backends like prometheus, but the unit was redundant. So the requested amount and subscribers unit suffixes have been dropped (separator may vary depending on the metrics backend).

    โšก๏ธ Processors and sinks update considerations

    The FluxIdentityProcessor and Processors classes that were introduced in M1 have been rolled back, and Sinks reworked (#2218)

    ๐Ÿšš The ultimate goal is to remove the need for processors (or greatly diminish it) and adding processor-related APIs was deemed counterproductive...

    The sinks from operators (FluxSinks and MonoSink in create) no longer have a common interface with the "standalone" sinks. The hierarchy has been simplified and we now have 4 uncorrelated interfaces: FluxSink and MonoSink (back to what they were in 3.3, oriented towards subscription-like usage) versus Sinks.One and Sinks.Many (both oriented towards publisher-like usage).

    ๐Ÿ— Sinks allow building the later two, with a tiered approach:

    1. chose many() vs one() vs empty()
    2. Then on many(), chose a flavor (unicast(), multicast(), replay())
    3. Finally, fine tune the flavor to obtain a sink (eg. Sinks.many().multicast().onBackpressureError())

    These can also be converted to processors efficiently, as their (private) implementation also implement FluxProcessor/MonoProcessor. NB: to convert from a Sinks.Many/Sinks.One to a processor where a processor is still relevant, use FluxProcessor.fromSink and MonoProcessor.fromSink.

    The MonoProcessor class used to be concrete but final. It is now abstract (#1053, #2296)

    ๐Ÿšฆ Most of its implementation has been moved to package-private NextProcessor. This will allow to introduce more flavors of MonoProcessor, focusing that class on signaling a Processor<A, B> that is also a Mono<B>.

    A new flavor VoidProcessor, has been also immediately added. It backs Sinks.empty() with less overhead than the old MonoProcessor.

    Sinks.Many and Sinks.One APIs

    Contrary to operator sinks, these two sinks are to be used more like publisher than like subscribers.

    That is to say, they are expected to be passed down asFlux() or asMono(), so generally should accommodate multiple subscribers (except for Sinks.many().unicast()).

    As a result they expose a different and more restrained API. Most relevant methods are the emitXxx methods, which are not fluent but return an Emission enum.
    ๐Ÿ‘€ They can be seen as a "best effort" API, indicating failure through the enum rather than an exception (comparable to Queue#offer vs Queue#add in the JDK).

    ๐Ÿฑ โš ๏ธ Consequently, the Emission enum should be checked to detect error cases.

    ๐Ÿ”ง For instance, the EmitterProcessor used to sleep when its configured buffer was full and users continued trying to emit, effectively busy-looping until the internal queue would accept the pushed valued. This is effectively blocking, and can be problematic (#2049).

    The equivalent sink, Sinks.many().multicast().onBackpressureBuffer(), doesn't sleep/block but immediately returns Emission.FAIL_OVERFLOW.

    Note that to get the old behavior, one can manually loop on the emitNext attempt until it returns Emission.OK, sleeping for a few nanoseconds when it returns FAIL_OVERFLOW. But caller could also decide to fail after a number of retries rather than an infinite loop for instance...

    Context APIs

    ContextView introduction (#2279)

    Knowing when a Context can be "written" vs when it cannot is complicated by the fact that operators that aim at exposing the contextual data only for reading still expose the full Context API, with write methods.

    In M2, we introduce ContextView, a superinterface to Context that only bears the read methods of the context.

    ๐Ÿšš Operators that expose the context for the purpose of reading from it (and where trying to put or remove data would effectively be a no-op) now have an alternative exposing the ContextView, deprecating the old way:

    • deferWithContext(Function<Context, P>) is superseded by deferContextual(Function<ContextView, P>)
    • we introduce transformDeferredContextual(BiFunction<P1, ContextView, P2>)
    • ๐Ÿšฆ Signal#getContext() is superseded by Signal#getContextView()
    • ๐Ÿ—„ Mono.subscriberContext() is deprecated. No replacement is planned as deferContextual(Mono::just) is a readily available replacement.

    A Context is already a ContextView. In case an API presents a ContextView but one really need a Context instead, use Context.of(ContextView).

    ๐Ÿ‘Œ Improve naming of context operators (#2148)

    ๐Ÿ—„ In 3.3, context operators are a bit confusing since they're only differentiated by their inputs. In 3.4 we deprecate these confusing names and provide alternatives:

    • ๐Ÿšš Mono.subscriberContext() is to be removed. Use Mono.deferContextual(Mono::just) instead, or even better try to rewrite the code to fully utilize the capacities of deferContextual and transformDeferredContextual operators (which could allow reading from the ContextView only once).
    • โž• subscriberContext(Context) is superseded by contextWrite(ContextView) (which better conveys that the entries in the input are added to the current Context)
    • subscriberContext(Function<Context, Context>) is superseded by contextWrite(Function<Context, Context>)

    โšก๏ธ Other update considerations

    • ๐Ÿšš Methods and classes deprecated in 3.3 have been removed (#2277)
      • to help with the retryWhen migration from legacy throwable-based function, a second adapter has been added (in both M2 and 3.3 latest release): Retry.withThrowable(Function<Flux<Throwable>, Publisher<?>>)
    • ๐Ÿ—„ subscribe(..., Consumer<Subscription>) variant has been deprecated (#1825):
      • too often, people misuse it to capture or log the Subscription but forget to request it, resulting in hangs
    • Mono.subscribe(valueHandler, errorHandler) now passed errors in the valueHandler to the errorHandler (#1995)
    • โฑ All instances of Scheduler are now expected to require start() call before usage (#1789)
      • the impact is limited as Schedulers.Factory-produced instances are started automatically by Schedulers factory methods (eg. Schedulers.parallel() or even Schedulers.newBoundedElastic())
    • ๐Ÿšฆ when encountering onError signal AFTER onComplete/onError, operators will now default to only logging the extraneous Throwable (#1431)
      • Same with a sequence that is subscribe without a error handler (#2176).
      • This can be tuned by setting a Hooks.onErrorDropped. The old behavior of throwing would cause issues and used to break the rules of Reactive Streams
    • All operators that take a Duration now do their best to deal with it in nanosecond precision (#1734)
      • However this means that the maximum interpretable Duration is now Duration.ofNanos(Long.MAX_VALUE) (down from ofMillis(Long.MAX_VALUE). This leaves us with 296 years as the maximum duration, which should be enough for all intents and purposes...
    • โฑ Schedulers.newElastic and Schedulers.elastic() are now deprecated, to be removed in 3.5

    ๐Ÿฑ โœจ New features and improvements

    • ๐Ÿ›  fix #2253 redesign how MetricsRegistry is configured: one can now centrally chose the registry to use instead of micrometer's globalRegistry
    • ๐Ÿ›  fix #2058 identify operators with scheduler through new scannable property (#2123)
    • ๐Ÿ‘ Allow "0" prefetch value in concatMap (#2202)
    • ๐Ÿ›  fix #2220 Log context access in FINE(ST) level and with correct prefix
    • ๐Ÿ‘ป Lazily instantiate exception in Mono#repeatWhenEmpty (#2221)

    ๐Ÿ“š ๐Ÿ“– Documentation, Tests and Build

    • ๐Ÿ“š various cleanups in code, documentation, tests and build
    • [doc] Document Android 21 desugaring options (#2232)

    ๐Ÿš€ ๐Ÿ‘ Thanks to the following contributors that also participated to this release

    @cnabro, @OlegDokuka, @seants, @yschimke, @robotmrv