Reactor Core v3.4.0 Release Notes

Release Date: 2020-10-27 // about 4 years ago
  • ๐Ÿš€ Reactor-Core 3.4.0 is part of 2020.0.0 Release Train (codename Europium).

    ๐Ÿš€ This is the first GA release of Europium ๐ŸŽ‰

    This note focuses on 3.4.0 proper, curating changes from across all milestones.
    ๐Ÿš€ Note that the 3.4.0 effort was started right after 3.3.5.RELEASE, so this version also contains changes from 3.3.6 to 3.3.11 (see the end of the notes for links).

    ๐Ÿฑ โš ๏ธ Known Issues

    • ๐Ÿ‘ป the firstWithValue new operator has an undocumented way of checking the cause of failure for individual sources, but it uses a suppressed exception rather than a cause. Please refrain from using that, as we'll switch to initCause in 3.4.1 (and properly document the behavior, setting it in stone) => #2459

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

    ๐Ÿฑ โš ๏ธ ๐Ÿ—‘๏ธ Removals

    • โฑ Schedulers deprecated back in 3.3 have been removed (WorkQueueProcessor and TopicProcessor, #2208)
    • ๐Ÿ‘€ Previously deprecated methods and types have been removed (see #2277)

    ๐Ÿฑ โš ๏ธ โŒ› Deprecations

    • โฑ Schedulers.elastic() and variants are deprecated, to be removed in 3.5 (#1893)
    • ๐Ÿšš FluxProcessor (as well as all concrete implementations of it) are now deprecated and will be removed in 3.5 (#2188, #2431)
      • see the main focus section below for more details
    • ๐Ÿšš [api-change] MonoProcessor is made abstract and deprecated, to be removed in 3.5 (#2188, #1053 / f04bede, #2431)
      • see the main focus section below for more details
    • ๐Ÿšš Flux.subscribe(Consumer<Subscription>) variant is deprecated, to be removed in 3.5 (#1825)
    • ๐Ÿšฆ first is deprecated to be renamed to firstWithSignal (see firstWithValue in the new features below). The old deprecated method will be removed in 3.5 (#2173)
    • Context changes (#2282):
      • Operators exposing the Context at points where there is no use attempting to use write methods are deprecated and will be removed in 3.5 (#2293, 7d6c862)
      • these all receive a ContextView-based variant (see new features below)
      • deferWithContext => deferContextual with a ContextView
      • Signal.getContext() => Signal.getContextView()
      • Operators modifying the Context are renamed to be clearer. Old names are deprecated and will be removed in 3.5 (see new feature below, #2148, eed93d6)

    ๐Ÿฑ โš ๏ธ โ™ป๏ธ Behavior Changes

    • Most operators that take a Duration and convert it to milliseconds now use nanosecond precision (#1734)
    • ๐Ÿ”Š Operators.onErrorDropped no longer throws, but only logs the dropped exception (#1431)
    • ๐Ÿšฆ When an onError signal reaches the last (lambda based) subscriber in a chain which doesn't have an error handler, the exception is no longer thrown but only logged (#2176)
    • โฑ Vanilla implementations of Scheduler now initiate their backing infrastructure and apply Schedulers decorator in start() rather than the constructor (#1778)
      • start() is also now implicitly called when instantiating a Scheduler via the Schedulers.Factory
      • Allows to avoid this leaking from constructor
      • โš ๏ธ Custom implementations should similarly initialized and optionally decorate in start()
    • EmitterProcessor with a full backpressure queue no longer blocks until data is requested, but fails fast (#2049)
    • Metrics changes
      • Change in how metric meters/tags are named in accordance with the name operator (#1928, #2302)
      • flow tag has been removed entirely
      • using the name({String}) operator will replace reactor. prefix in meter names with the user-provided {String}.
      • โš ๏ธ using custom tags via the tags(String...) operator, but not name(String) still creates a sparse set of tags in the application which could be rejected by some metrics backend
      • Change name of some meters that used to include (redundant) unit names (#1807)
      • reactor_requested_requested_amount => {name}_requested
      • reactor_subscribed_subscribers => {name}_subscribed
      • notice these meters also take the user-provided {name} into account from the above change
      • Differentiate empty vs valued sequences in metrics via the status tag, introducing completedEmpty value (#1803)

    ๐Ÿฑ โš ๏ธ โœจ Main focus: Sinks

    ๐Ÿ—„ Sinks are a new API constructed to replace the FluxProcessor and MonoProcessor APIs, which have been deprecated (#2431, #1053).

    We distinguish Sinks.Many<T> (a Flux-like sink), Sinks.One<T> (a Mono-like sink) and Sinks.Empty (a Mono<Void>-like sink).

    These sinks are constructed through specs under the Sinks class. They expose a two-faced API:

    • methods starting with tryEmit are atomic, never throw (#2319, #2329, #2336, #2426) and immediately return an EmitResult enum (originally named Emission) indicating various variations of success or error (#2319, #2338)
    • 0๏ธโƒฃ methods starting with emit attempt to provide an easier facade over the above, take an EmitFailureHandler allowing to fine tune some aspects of the default behavior (#2377)

    0๏ธโƒฃ By default, the instances are "serialized", detecting parallel usage and failing fast in tryEmit (#2342, #2410, #2365, 19fc1ba, #2412).
    The vanilla reactor sink implementations of emit API terminate with the equivalent of an emitError when parallel usage is detected (#2365) but by using the EmitFailureHandler to drive the sink to retry, possibly with a small amount of sleeping, it should be possible to optimistically get rid of the contention.

    It is also possible to get a low-overhead, low-protection instance of vanilla sinks by using the Sinks.unsafe() spec (#2418).
    This doesn't detect parallel usage, so it must be used in a responsible way (in a context where the Reactive Streams contract is already externally enforced).

    Sinks also expose a asFlux() view (or asMono(), as relevant) for them to be passed to downstream and subscribed, and also expose a bit of state through currentSubscriberCount() getter (#2372) and by being Scannable (at a minimum for TERMINATED and CANCELLED attributes, #2394).

    Compared to existing processors, most flavors have a sink equivalent except DirectProcessor, with two close cousins under Sinks.many().multicast() (#2392, #2451):

    • directAllOrNothing will ignore tryEmitNext attempts when at least one subscriber doesn't have enough demand,
    • directBestEffort will instead push to the subset of subscribers with demand and drop from the perspective of slow subscribers.

    ๐Ÿ‘€ This differs from DirectProcessor in the sense that both these sinks are kept open in such a situation, so slow subscribers that increase their request will start seeing values pushed after that (instead of being immediately terminated in DirectProcessor).

    There is also a new flavor: an onBackpressureError() variant of the unicast Sink (#2347)

    ๐Ÿ›  ๐Ÿž Bugfixes

    ๐Ÿš€ See links to Dysprosium releases (3.3.6 to 3.3.11) at the end.

    ๐Ÿฑ โœจ New features

    • concatMap now has a variant with 0 prefetch (#2202)
    • ๐Ÿ’… All Scannable operators answer the new Attr.RUN_STYLE attribute with a RunStyle enum (#2058, #2123)
      • the enum allows to identify operators that run synchronously. Attribute is accessible both at Publisher and Subscriber level
      • RunStyle.SYNC guarantees the operator doesn't change threads
      • RunStyle.ASYNC indicates the operator MAY change threads
      • RunStyle.UNKNOWN (the default) indicates there's no available information (eg. due to the operator wrapping an arbitrary Publisher)
    • ๐Ÿ”ง A Reactor-wide Micrometer MetricsRegistry other than the global one can now be configured (#2253, 64fd556)
      • Use Metrics.MicrometerConfiguration#useRegistry for that purpose
      • The above will attempt to load Micrometer classes, so Micrometer MUST be on the classpath
    • ๐Ÿ†• New reactive context features (#2282):
      • Context now extends a simplified ContextView API which only expose the read methods (#2279, #2293)
      • the goal is to avoid exposing a seemingly writable (copy-on-write) Context when the only meaningful operations are read operations
      • exposed in Signal#getContextView(), deferContextual operator (variant to deferWithContext) and new transformDeferredContextual operator
      • Added transformDeferredContextual operator that takes a BiFunction, allowing to access the ContextView in the transformation (#2280 then renamed in #2293)
      • Mono.subscribe(valueConsumer, errorConsumer, ...) error Consumer now gets notified of exceptions thrown by the value Consumer (#1995)
      • Renamed Context operators with clearer names (#2148, eed93d6)
      • Mono.subscriberContext() is not aliased, we now advise to use Mono.deferContextual(Mono::just) to get the exact same behavior
      • subscriberContext(Context) is replaced by contextWrite(ContextView) (notice the use of ContextView)
      • subscriberContext(Function) is replaced by contextWrite(Function)
    • โž• Added method to snapshot factory+global schedulers (#2325, #2326, f9c7993)
      • use Schedulers.setFactoryWithSnapshot(Factory) to get a Snapshot object while replacing the Factory
      • reset the old factory by using resetFrom(Snapshot)
      • this is used by the reactor-test VirtualTimeScheduler to reset previously customized factories
    • The ParallelFlux.subscribe(array) method is now public to allow delegation in wrappers (#2328)
    • ๐Ÿšฆ The Retry object driving #retryWhen operator can now store user-provided state in the form of a ContextView, which is exposed through RetrySignal (#2312, bd8db8a)
    • The GroupedFlux#key() method is now marked as @NonNull (#2397)
    • โž• Added firstWithValue factory operator (#2173, 41c937f)
      • like first, it let multiple sources compete
      • except it prioritizes valued sources: empty sources are considered irrelevant
      • if no source completes with a value, a NoSuchElementException is thrown

    ๐Ÿ“ˆ Improvements

    • Have push(emitter) delegate to push(emitter, backpressure) (#2177)
    • ๐Ÿ‘ป Lazily instantiate exception in Mono#repeatWhenEmpty (#2221)
    • ๐ŸŒฒ log() will log context access at FINE(ST) level and now uses the more correct currentContext prefix (#2220)
    • Mono#materialize() now takes into account that the source IS a Mono, so it doesn't cancel() it when materializing its onNext (#2424, 765bfe8)
      • this could be considered a behavior change, but the previous behavior was unnecessary. cancelling the result of the materialization still propagates to the source.

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

    • [doc] fix #2170 Make parallel runOn methods doc consistent about work stealing
    • [doc] Document Android 21 desugaring options (#2232)
    • ๐Ÿ— [build] #2237 remove most compilation warnings
    • [doc] fix #2136 Turn discard/errorMode javadoc tags into plain paragraphs
    • ๐Ÿ— [build] fix #2400 use a different name for the jcstress jar
    • ๐Ÿ— [build] Remove compilation warnings related to jsr305 (85da74b)

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

    @AayushyaVajpayee, @camsteffen, @cnabro, @hamidshahid, @Inego, @jonenst, @josemalonsom, @OlegDokuka, @robotmrv, @seants, @smaldini, @steppedreckoner, @yschimke

    ๐Ÿ”— Links to Dysprosium release notes during 3.4.0 development effort

    ๐Ÿš€ All changes from these releases have been forward-merged into 3.4.0: