Reactor Core v3.4.1 Release Notes

Release Date: 2020-12-08 // over 3 years ago
  • 🚀 Reactor-Core 3.4.1 is part of 2020.0.2 Release Train (Europium SR2).

    🛠 This service release contains bugfixes and new features.

    🚀 This note focuses on changes specific to this version, but all changes from 3.2.22.RELEASE and 3.3.12.RELEASE are also included.

    ⚡️ ⚠️ Update considerations and deprecations

    • 🛠 fix #2459 Add a composite of all source as cause to firstWithValue error
      • this was overlooked when merging the new operator in 3.4.0, so we decided it was fresh enough that the slight behavior change would be acceptable

    🍱 ✨ New features and improvements

    • 🛠 fix #2195 Add timed() operator
      • this new operator exposes various timing information on a Timed wrapper object, allowing richer output of Duration compared to elapsed() and timestamp() (which use Tuple2 and Long)
    • 👀 [reactor-test] fix #2269 Add test util to capture logs in early-established loggers (see also #2512)
      • LoggerUtils allows the early installation of a Logger factory that will forward to a classic factory AND let the user activate a logging indirection later on during tests
      • this allows a TestLogger to capture internal logs from operators that were classloaded earlier than the unit test
    • ✅ [reactor-test] fix #1518 Add cold variants of TestPublisher
      • cold variants better behave wrt backpressure and can replay to multiple subscribers at various stages
      • the old behavior (of ignoring requested amount) is still available through a createColdNonCompliant variant
    • ✅ [reactor-test] When asserting Context in StepVerifier, say from which operator the asserted Context was taken (#2518)

    🍱 🐞 Bug fixes

    • 🛠 Make RetryWhenMainSubscriber#onError's calls serial (#2499, Fixes #2488)
    • 🛠 fix #2513 ReplayProcessor now correctly pass nanos to buffer, not ms
    • 🚀 from 3.3.12.RELEASE:
      • fix #2519 Cancel propagation on empty collectXxx
    • 🚀 from 3.2..22.RELEASE:
      • fix #2498 Exception thrown in Flux.handle causes hanging in fused case

    📚 📖 Documentation, Tests and Build

    • 📚 Various documentation improvements (#2463, #2457, #2469)
    • 🛠 fix #2468 Review and polish deprecation suppressions
    • 🛠 fix #2502 Generate OSGI Bundle-Version from Europium+ scheme

    🚀 👍 Thanks to the following contributors that also participated to this release

    @dowgiallom, @seants


Previous changes from v3.4.0

  • 🚀 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: