Index: 3rdParty_sources/reactive-streams/org/reactivestreams/FlowAdapters.java =================================================================== diff -u -r2d6722d97aad801e2f7db229945ae3dab6ec8576 -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactive-streams/org/reactivestreams/FlowAdapters.java (.../FlowAdapters.java) (revision 2d6722d97aad801e2f7db229945ae3dab6ec8576) +++ 3rdParty_sources/reactive-streams/org/reactivestreams/FlowAdapters.java (.../FlowAdapters.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,13 +1,6 @@ -/************************************************************************ - * Licensed under Public Domain (CC0) * - * * - * To the extent possible under law, the person who associated CC0 with * - * this code has waived all copyright and related or neighboring * - * rights to this code. * - * * - * You should have received a copy of the CC0 legalcode along with this * - * work. If not, see .* - ************************************************************************/ +/*************************************************** + * Licensed under MIT No Attribution (SPDX: MIT-0) * + ***************************************************/ package org.reactivestreams; Index: 3rdParty_sources/reactive-streams/org/reactivestreams/Processor.java =================================================================== diff -u -r2d6722d97aad801e2f7db229945ae3dab6ec8576 -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactive-streams/org/reactivestreams/Processor.java (.../Processor.java) (revision 2d6722d97aad801e2f7db229945ae3dab6ec8576) +++ 3rdParty_sources/reactive-streams/org/reactivestreams/Processor.java (.../Processor.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,18 +1,11 @@ -/************************************************************************ - * Licensed under Public Domain (CC0) * - * * - * To the extent possible under law, the person who associated CC0 with * - * this code has waived all copyright and related or neighboring * - * rights to this code. * - * * - * You should have received a copy of the CC0 legalcode along with this * - * work. If not, see .* - ************************************************************************/ +/*************************************************** + * Licensed under MIT No Attribution (SPDX: MIT-0) * + ***************************************************/ package org.reactivestreams; /** - * A Processor represents a processing stage—which is both a {@link Subscriber} + * A {@link Processor} represents a processing stage—which is both a {@link Subscriber} * and a {@link Publisher} and obeys the contracts of both. * * @param the type of element signaled to the {@link Subscriber} Index: 3rdParty_sources/reactive-streams/org/reactivestreams/Publisher.java =================================================================== diff -u -r2d6722d97aad801e2f7db229945ae3dab6ec8576 -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactive-streams/org/reactivestreams/Publisher.java (.../Publisher.java) (revision 2d6722d97aad801e2f7db229945ae3dab6ec8576) +++ 3rdParty_sources/reactive-streams/org/reactivestreams/Publisher.java (.../Publisher.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,24 +1,17 @@ -/************************************************************************ - * Licensed under Public Domain (CC0) * - * * - * To the extent possible under law, the person who associated CC0 with * - * this code has waived all copyright and related or neighboring * - * rights to this code. * - * * - * You should have received a copy of the CC0 legalcode along with this * - * work. If not, see .* - ************************************************************************/ +/*************************************************** + * Licensed under MIT No Attribution (SPDX: MIT-0) * + ***************************************************/ package org.reactivestreams; /** * A {@link Publisher} is a provider of a potentially unbounded number of sequenced elements, publishing them according to * the demand received from its {@link Subscriber}(s). *

- * A {@link Publisher} can serve multiple {@link Subscriber}s subscribed {@link #subscribe(Subscriber)} dynamically + * A {@link Publisher} can serve multiple {@link Subscriber}s subscribed {@link Publisher#subscribe(Subscriber)} dynamically * at various points in time. * - * @param the type of element signaled. + * @param the type of element signaled */ public interface Publisher { @@ -32,7 +25,7 @@ * A {@link Subscriber} should only subscribe once to a single {@link Publisher}. *

* If the {@link Publisher} rejects the subscription attempt or otherwise fails it will - * signal the error via {@link Subscriber#onError}. + * signal the error via {@link Subscriber#onError(Throwable)}. * * @param s the {@link Subscriber} that will consume signals from this {@link Publisher} */ Index: 3rdParty_sources/reactive-streams/org/reactivestreams/Subscriber.java =================================================================== diff -u -r2d6722d97aad801e2f7db229945ae3dab6ec8576 -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactive-streams/org/reactivestreams/Subscriber.java (.../Subscriber.java) (revision 2d6722d97aad801e2f7db229945ae3dab6ec8576) +++ 3rdParty_sources/reactive-streams/org/reactivestreams/Subscriber.java (.../Subscriber.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,13 +1,6 @@ -/************************************************************************ - * Licensed under Public Domain (CC0) * - * * - * To the extent possible under law, the person who associated CC0 with * - * this code has waived all copyright and related or neighboring * - * rights to this code. * - * * - * You should have received a copy of the CC0 legalcode along with this * - * work. If not, see .* - ************************************************************************/ +/*************************************************** + * Licensed under MIT No Attribution (SPDX: MIT-0) * + ***************************************************/ package org.reactivestreams; @@ -24,9 +17,10 @@ *

* Demand can be signaled via {@link Subscription#request(long)} whenever the {@link Subscriber} instance is capable of handling more. * - * @param the type of element signaled. + * @param the type of element signaled */ public interface Subscriber { + /** * Invoked after calling {@link Publisher#subscribe(Subscriber)}. *

@@ -36,8 +30,7 @@ *

* The {@link Publisher} will send notifications only in response to {@link Subscription#request(long)}. * - * @param s - * {@link Subscription} that allows requesting data via {@link Subscription#request(long)} + * @param s the {@link Subscription} that allows requesting data via {@link Subscription#request(long)} */ public void onSubscribe(Subscription s); Index: 3rdParty_sources/reactive-streams/org/reactivestreams/Subscription.java =================================================================== diff -u -r2d6722d97aad801e2f7db229945ae3dab6ec8576 -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactive-streams/org/reactivestreams/Subscription.java (.../Subscription.java) (revision 2d6722d97aad801e2f7db229945ae3dab6ec8576) +++ 3rdParty_sources/reactive-streams/org/reactivestreams/Subscription.java (.../Subscription.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,13 +1,6 @@ -/************************************************************************ - * Licensed under Public Domain (CC0) * - * * - * To the extent possible under law, the person who associated CC0 with * - * this code has waived all copyright and related or neighboring * - * rights to this code. * - * * - * You should have received a copy of the CC0 legalcode along with this * - * work. If not, see .* - ************************************************************************/ +/*************************************************** + * Licensed under MIT No Attribution (SPDX: MIT-0) * + ***************************************************/ package org.reactivestreams; @@ -17,9 +10,9 @@ * It can only be used once by a single {@link Subscriber}. *

* It is used to both signal desire for data and cancel demand (and allow resource cleanup). - * */ public interface Subscription { + /** * No events will be sent by a {@link Publisher} until demand is signaled via this method. *

Index: 3rdParty_sources/reactor/reactor/adapter/JdkFlowAdapter.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/adapter/JdkFlowAdapter.java (.../JdkFlowAdapter.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/adapter/JdkFlowAdapter.java (.../JdkFlowAdapter.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -57,9 +57,9 @@ } private static class FlowPublisherAsFlux extends Flux implements Scannable { - private final java.util.concurrent.Flow.Publisher pub; + private final Flow.Publisher pub; - private FlowPublisherAsFlux(java.util.concurrent.Flow.Publisher pub) { + private FlowPublisherAsFlux(Flow.Publisher pub) { this.pub = pub; } @@ -172,4 +172,4 @@ } JdkFlowAdapter(){} -} +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/Exceptions.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/Exceptions.java (.../Exceptions.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/Exceptions.java (.../Exceptions.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,8 @@ import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import reactor.core.publisher.Flux; +import reactor.util.Logger; +import reactor.util.Loggers; import reactor.util.annotation.Nullable; import reactor.util.retry.Retry; @@ -36,6 +38,8 @@ */ public abstract class Exceptions { + private static final Logger LOGGER = Loggers.getLogger(Exceptions.class); + /** * A common error message used when a reactive streams source doesn't seem to respect * backpressure signals, resulting in an operator's internal queue to be full. @@ -99,6 +103,18 @@ } /** + * Wrap a {@link Throwable} delivered via {@link org.reactivestreams.Subscriber#onError(Throwable)} + * from an upstream {@link org.reactivestreams.Publisher} that itself + * emits {@link org.reactivestreams.Publisher}s to distinguish the error signal from + * the inner sequence's processing errors. + * @param throwable the source sequence {@code error} signal + * @return {@link SourceException} + */ + public static Throwable wrapSource(Throwable throwable) { + return new SourceException(throwable); + } + + /** * Create a composite exception that wraps the given {@link Throwable Throwable(s)}, * as suppressed exceptions. Instances create by this method can be detected using the * {@link #isMultiple(Throwable)} check. The {@link #unwrapMultiple(Throwable)} method @@ -413,6 +429,68 @@ } /** + * Check if a {@link Throwable} is considered by Reactor as Jvm Fatal and would be thrown + * by both {@link #throwIfFatal(Throwable)} and {@link #throwIfJvmFatal(Throwable)}. + * This is a subset of {@link #isFatal(Throwable)}, namely: + *

    + *
  • {@link VirtualMachineError}
  • + *
  • {@link ThreadDeath}
  • + *
  • {@link LinkageError}
  • + *
+ *

+ * Unless wrapped explicitly, such exceptions would always be thrown by operators instead of + * propagation through onError, potentially interrupting progress of Flux/Mono sequences. + * When they occur, the JVM itself is assumed to be in an unrecoverable state, and so is Reactor. + * + * @see #throwIfFatal(Throwable) + * @see #throwIfJvmFatal(Throwable) + * @see #isFatal(Throwable) + * @param t the {@link Throwable} to check + * @return true if the throwable is considered Jvm Fatal + */ + public static boolean isJvmFatal(@Nullable Throwable t) { + return t instanceof VirtualMachineError || + t instanceof ThreadDeath || + t instanceof LinkageError; + } + + /** + * Check if a {@link Throwable} is considered by Reactor as Fatal and would be thrown by + * {@link #throwIfFatal(Throwable)}. + *

    + *
  • {@code BubblingException} (as detectable by {@link #isBubbling(Throwable)})
  • + *
  • {@code ErrorCallbackNotImplemented} (as detectable by {@link #isErrorCallbackNotImplemented(Throwable)})
  • + *
  • {@link #isJvmFatal(Throwable) Jvm Fatal exceptions} + *
      + *
    • {@link VirtualMachineError}
    • + *
    • {@link ThreadDeath}
    • + *
    • {@link LinkageError}
    • + *
    + *
  • + *
+ *

+ * Unless wrapped explicitly, such exceptions would always be thrown by operators instead of + * propagation through onError, potentially interrupting progress of Flux/Mono sequences. + * When they occur, the assumption is that Reactor is in an unrecoverable state (notably + * because the JVM itself might be in an unrecoverable state). + * + * @see #throwIfFatal(Throwable) + * @see #isJvmFatal(Throwable) + * @param t the {@link Throwable} to check + * @return true if the throwable is considered fatal + */ + public static boolean isFatal(@Nullable Throwable t) { + return isFatalButNotJvmFatal(t) || isJvmFatal(t); + } + + /** + * Internal intermediate test that only detect Fatal but not Jvm Fatal exceptions. + */ + static boolean isFatalButNotJvmFatal(@Nullable Throwable t) { + return t instanceof BubblingException || t instanceof ErrorCallbackNotImplemented; + } + + /** * Throws a particular {@code Throwable} only if it belongs to a set of "fatal" error * varieties. These varieties are as follows:

    *
  • {@code BubblingException} (as detectable by {@link #isBubbling(Throwable)})
  • @@ -422,13 +500,17 @@ * @param t the exception to evaluate */ public static void throwIfFatal(@Nullable Throwable t) { - if (t instanceof BubblingException) { - throw (BubblingException) t; + if (t == null) { + return; } - if (t instanceof ErrorCallbackNotImplemented) { - throw (ErrorCallbackNotImplemented) t; + if (isFatalButNotJvmFatal(t)) { + LOGGER.warn("throwIfFatal detected a fatal exception, which is thrown and logged below:", t); + throw (RuntimeException) t; } - throwIfJvmFatal(t); + if (isJvmFatal(t)) { + LOGGER.warn("throwIfFatal detected a jvm fatal exception, which is thrown and logged below:", t); + throw (Error) t; + } } /** @@ -440,15 +522,14 @@ * @param t the exception to evaluate */ public static void throwIfJvmFatal(@Nullable Throwable t) { - if (t instanceof VirtualMachineError) { - throw (VirtualMachineError) t; + if (t == null) { + return; } - if (t instanceof ThreadDeath) { - throw (ThreadDeath) t; + if (isJvmFatal(t)) { + LOGGER.warn("throwIfJvmFatal detected a jvm fatal exception, which is thrown and logged below:", t); + assert t instanceof Error; + throw (Error) t; } - if (t instanceof LinkageError) { - throw (LinkageError) t; - } } /** @@ -656,6 +737,23 @@ private static final long serialVersionUID = 2491425227432776143L; } + /** + * A {@link Throwable} that wraps the actual {@code cause} delivered via + * {@link org.reactivestreams.Subscriber#onError(Throwable)} in case of + * {@link org.reactivestreams.Publisher}s that themselves emit items of type + * {@link org.reactivestreams.Publisher}. This wrapper is used to distinguish + * {@code error}s delivered by the upstream sequence from the ones that happen via + * the inner sequence processing chain. + */ + public static class SourceException extends ReactiveException { + + SourceException(Throwable cause) { + super(cause); + } + + private static final long serialVersionUID = 5747581575202629465L; + } + static final class ErrorCallbackNotImplemented extends UnsupportedOperationException { ErrorCallbackNotImplemented(Throwable cause) { Index: 3rdParty_sources/reactor/reactor/core/Scannable.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/Scannable.java (.../Scannable.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/Scannable.java (.../Scannable.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2017-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,9 +18,14 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.Spliterators; import java.util.function.Function; import java.util.regex.Pattern; @@ -33,6 +38,7 @@ import reactor.core.scheduler.Scheduler.Worker; import reactor.util.annotation.Nullable; import reactor.util.function.Tuple2; +import reactor.util.function.Tuples; /** * A Scannable component exposes state in a non strictly memory consistent way and @@ -205,7 +211,7 @@ public static final Attr TERMINATED = new Attr<>(false); /** - * A {@link Stream} of {@link reactor.util.function.Tuple2} representing key/value + * A {@link Stream} of {@link Tuple2} representing key/value * pairs for tagged components. Defaults to {@literal null}. */ public static final Attr>> TAGS = new Attr<>(null); @@ -588,25 +594,46 @@ } /** - * Visit this {@link Scannable} and its {@link #parents()} and stream all the - * observed tags + * Visit this {@link Scannable} and its {@link #parents()}, starting by the furthest reachable parent, + * and return a {@link Stream} of the tags which includes duplicates and outputs tags in declaration order + * (grandparent tag(s) > parent tag(s) > current tag(s)). + *

    + * Tags can only be discovered until no parent can be inspected, which happens either + * when the source publisher has been reached or when a non-reactor intermediate operator + * is present in the parent chain (i.e. a stage that is not {@link Scannable} for {@link Attr#PARENT}). * - * @return the stream of tags for this {@link Scannable} and its parents + * @return the stream of tags for this {@link Scannable} and its reachable parents, including duplicates + * @see #tagsDeduplicated() */ default Stream> tags() { - Stream> parentTags = - parents().flatMap(s -> s.scan(Attr.TAGS)); + List sources = new LinkedList<>(); - Stream> thisTags = scan(Attr.TAGS); - - if (thisTags == null) { - return parentTags; + Scannable aSource = this; + while (aSource != null && aSource.isScanAvailable()) { + sources.add(0, aSource); + aSource = aSource.scan(Attr.PARENT); } - return Stream.concat( - thisTags, - parentTags - ); + return sources.stream() + .flatMap(source -> source.scanOrDefault(Attr.TAGS, Stream.empty())); } -} + /** + * Visit this {@link Scannable} and its {@link #parents()}, starting by the furthest reachable parent, + * deduplicate tags that have a common key by favoring the value declared last (current tag(s) > parent tag(s) > grandparent tag(s)) + * and return a {@link Map} of the deduplicated tags. Note that while the values are the "latest", the key iteration order reflects + * the tags' declaration order. + *

    + * Tags can only be discovered until no parent can be inspected, which happens either + * when the source publisher has been reached or when a non-reactor intermediate operator + * is present in the parent chain (i.e. a stage that is not {@link Scannable} for {@link Attr#PARENT}). + * + * @return a {@link Map} of deduplicated tags from this {@link Scannable} and its reachable parents + * @see #tags() + */ + default Map tagsDeduplicated() { + return tags().collect(Collectors.toMap(Tuple2::getT1, Tuple2::getT2, + (s1, s2) -> s2, LinkedHashMap::new)); + } + +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/observability/DefaultSignalListener.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/observability/DefaultSignalListener.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/observability/DefaultSignalListener.java (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2022 VMware Inc. or its affiliates, All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reactor.core.observability; + +import reactor.core.Fuseable; +import reactor.core.publisher.SignalType; + +/** + * A default implementation of a {@link SignalListener} with all the handlers no-op. + * + * @author Simon Baslé + */ +public abstract class DefaultSignalListener implements SignalListener { + + /** + * This listener actually captures the negotiated fusion + * mode (if any) and exposes this information to child classes via #getFusionMode(). + */ + int fusionMode = Fuseable.NONE; + + @Override + public void doFirst() throws Throwable { + } + + @Override + public void doFinally(SignalType terminationType) throws Throwable { + } + + @Override + public void doOnSubscription() throws Throwable { + } + + @Override + public void doOnFusion(int negotiatedFusion) throws Throwable { + this.fusionMode = negotiatedFusion; + } + + /** + * Return the fusion mode negotiated with the source: {@link Fuseable#SYNC} and {@link Fuseable#ASYNC}) as relevant + * if some fusion was negotiated. {@link Fuseable#NONE} if fusion was never requested, or if it couldn't be negotiated. + * + * @return the negotiated fusion mode, if any + */ + protected int getFusionMode() { + return fusionMode; + } + + @Override + public void doOnRequest(long requested) throws Throwable { + } + + @Override + public void doOnCancel() throws Throwable { + } + + @Override + public void doOnNext(T value) throws Throwable { + } + + @Override + public void doOnComplete() throws Throwable { + } + + @Override + public void doOnError(Throwable error) throws Throwable { + } + + @Override + public void doAfterComplete() throws Throwable { + } + + @Override + public void doAfterError(Throwable error) throws Throwable { + } + + @Override + public void doOnMalformedOnNext(T value) throws Throwable { + } + + @Override + public void doOnMalformedOnComplete() throws Throwable { + } + + @Override + public void doOnMalformedOnError(Throwable error) throws Throwable { + } + + @Override + public void handleListenerError(Throwable listenerError) { + } +} Index: 3rdParty_sources/reactor/reactor/core/observability/SignalListener.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/observability/SignalListener.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/observability/SignalListener.java (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2022 VMware Inc. or its affiliates, All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reactor.core.observability; + +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.publisher.Operators; +import reactor.core.publisher.SignalType; +import reactor.util.context.Context; + +/** + * A listener which combines various handlers to be triggered per the corresponding {@link Flux} or {@link Mono} signals. + * This is similar to the "side effect" operators in {@link Flux} and {@link Mono}, but in a single listener class. + * {@link SignalListener} are created by a {@link SignalListenerFactory}, which is tied to a particular {@link Publisher}. + * Each time a new {@link Subscriber} subscribes to that {@link Publisher}, the factory creates an associated {@link SignalListener}. + *

    + * Both publisher-to-subscriber events and subscription events are handled. Methods are closer to the side-effect doOnXxx operators + * than to {@link Subscriber} and {@link Subscription} methods, in order to avoid misconstruing this for an actual Reactive Streams + * implementation. The actual downstream {@link Subscriber} and upstream {@link Subscription} are intentionally not exposed + * to avoid any influence on the observed sequence. + * + * @author Simon Baslé + */ +public interface SignalListener { + + /** + * Handle the very beginning of the {@link Subscriber}-{@link Publisher} interaction. + * This handler is invoked right before subscribing to the parent {@link Publisher}, as a downstream + * {@link Subscriber} has called {@link Publisher#subscribe(Subscriber)}. + *

    + * Once the {@link Publisher} has acknowledged with a {@link Subscription}, the {@link #doOnSubscription()} + * handler will be invoked before that {@link Subscription} is passed down. + * + * @see #doOnSubscription() + */ + void doFirst() throws Throwable; + + /** + * Handle terminal signals after the signals have been propagated, as the final step. + * Only {@link SignalType#ON_COMPLETE}, {@link SignalType#ON_ERROR} or {@link SignalType#CANCEL} can be passed. + * This handler is invoked AFTER the terminal signal has been propagated, and if relevant AFTER the {@link #doAfterComplete()} + * or {@link #doAfterError(Throwable)} events. If any doOnXxx handler throws, this handler is NOT invoked (see {@link #handleListenerError(Throwable)} + * instead). + * + * @see #handleListenerError(Throwable) + */ + void doFinally(SignalType terminationType) throws Throwable; + + /** + * Handle the fact that the upstream {@link Publisher} acknowledged {@link Subscription}. + * The {@link Subscription} is intentionally not exposed in order to avoid manipulation by the observer. + *

    + * While {@link #doFirst} is invoked right as the downstream {@link Subscriber} is registered, + * this method is invoked as the upstream answers back with a {@link Subscription} (and before that + * same {@link Subscription} is passed downstream). + * + * @see #doFirst() + */ + void doOnSubscription() throws Throwable; + + /** + * Handle the negotiation of fusion between two {@link reactor.core.Fuseable} operators. As the downstream operator + * requests fusion, the upstream answers back with the compatible level of fusion it can handle. This {@code negotiatedFusion} + * code is passed to this handler right before it is propagated downstream. + * + * @param negotiatedFusion the final fusion mode negotiated by the upstream operator in response to a fusion request + * from downstream + */ + void doOnFusion(int negotiatedFusion) throws Throwable; + + /** + * Handle a new request made by the downstream, exposing the demand. + *

    + * This is invoked before the request is propagated upstream. + * + * @param requested the downstream demand + */ + void doOnRequest(long requested) throws Throwable; + + /** + * Handle the downstream cancelling its currently observed {@link Subscription}. + *

    + * This handler is invoked before propagating the cancellation upstream, while {@link #doFinally(SignalType)} + * is invoked right after the cancellation has been propagated upstream. + * + * @see #doFinally(SignalType) + */ + void doOnCancel() throws Throwable; + + /** + * Handle a new value emission from the source. + *

    + * This handler is invoked before propagating the value downstream. + * + * @param value the emitted value + */ + void doOnNext(T value) throws Throwable; + + /** + * Handle graceful onComplete sequence termination. + *

    + * This handler is invoked before propagating the completion downstream, while both + * {@link #doAfterComplete()} and {@link #doFinally(SignalType)} are invoked after. + * + * @see #doAfterComplete() + * @see #doFinally(SignalType) + */ + void doOnComplete() throws Throwable; + + /** + * Handle onError sequence termination. + *

    + * This handler is invoked before propagating the error downstream, while both + * {@link #doAfterError(Throwable)} and {@link #doFinally(SignalType)} are invoked after. + * + * @param error the exception that terminated the sequence + * @see #doAfterError(Throwable) + * @see #doFinally(SignalType) + */ + void doOnError(Throwable error) throws Throwable; + + /** + * Handle graceful onComplete sequence termination, after onComplete has been propagated downstream. + *

    + * This handler is invoked after propagating the completion downstream, similar to {@link #doFinally(SignalType)} + * and unlike {@link #doOnComplete()}. + */ + void doAfterComplete() throws Throwable; + + /** + * Handle onError sequence termination after onError has been propagated downstream. + *

    + * This handler is invoked after propagating the error downstream, similar to {@link #doFinally(SignalType)} + * and unlike {@link #doOnError(Throwable)}. + * + * @param error the exception that terminated the sequence + */ + void doAfterError(Throwable error) throws Throwable; + + /** + * Handle malformed {@link Subscriber#onNext(Object)}, which are onNext happening after the sequence has already terminated + * via {@link Subscriber#onComplete()} or {@link Subscriber#onError(Throwable)}. + * Note that after this handler is invoked, the value is automatically {@link Operators#onNextDropped(Object, Context) dropped}. + *

    + * If this handler fails with an exception, that exception is {@link Operators#onErrorDropped(Throwable, Context) dropped} before the + * value is also dropped. + * + * @param value the value for which an emission was attempted (which will be automatically dropped afterwards) + */ + void doOnMalformedOnNext(T value) throws Throwable; + + /** + * Handle malformed {@link Subscriber#onError(Throwable)}, which means the sequence has already terminated + * via {@link Subscriber#onComplete()} or {@link Subscriber#onError(Throwable)}. + * Note that after this handler is invoked, the exception is automatically {@link Operators#onErrorDropped(Throwable, Context) dropped}. + *

    + * If this handler fails with an exception, that exception is {@link Operators#onErrorDropped(Throwable, Context) dropped} before the + * original onError exception is also dropped. + * + * @param error the extraneous exception (which will be automatically dropped afterwards) + */ + void doOnMalformedOnError(Throwable error) throws Throwable; + + /** + * Handle malformed {@link Subscriber#onComplete()}, which means the sequence has already terminated + * via {@link Subscriber#onComplete()} or {@link Subscriber#onError(Throwable)}. + *

    + * If this handler fails with an exception, that exception is {@link Operators#onErrorDropped(Throwable, Context) dropped}. + */ + void doOnMalformedOnComplete() throws Throwable; + + /** + * A special handler for exceptions thrown from all the other handlers. + * This method MUST return normally, i.e. it MUST NOT throw. + * When a {@link SignalListener} handler fails, callers are expected to first invoke this method then to propagate + * the {@code listenerError} downstream if that is possible, terminating the original sequence with the listenerError. + *

    + * Typically, this special handler is intended for a last chance at processing the error despite the fact that + * {@link #doFinally(SignalType)} is not triggered on handler errors. For example, recording the error in a + * metrics backend or cleaning up state that would otherwise be cleaned up by {@link #doFinally(SignalType)}. + * + * @param listenerError the exception thrown from a {@link SignalListener} handler method + */ + void handleListenerError(Throwable listenerError); + + /** + * In some cases, the tap operation should alter the {@link Context} exposed by the operator in order to store additional + * data. This method is invoked when the tap subscriber is created, which is between the invocation of {@link #doFirst()} + * and the invocation of {@link #doOnSubscription()}. Generally, only addition of new keys should be performed on + * the downstream original {@link Context}. Extra care should be exercised if any pre-existing key is to be removed + * or replaced. + * + * @param originalContext the original downstream operator's {@link Context} + * @return the {@link Context} to use and expose upstream + */ + default Context addToContext(Context originalContext) { + return originalContext; + } +} Index: 3rdParty_sources/reactor/reactor/core/observability/SignalListenerFactory.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/observability/SignalListenerFactory.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/observability/SignalListenerFactory.java (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2022 VMware Inc. or its affiliates, All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reactor.core.observability; + +import org.reactivestreams.Publisher; + +import reactor.util.context.ContextView; + +/** + * A factory for per-subscription {@link SignalListener}, exposing the ability to generate common state at publisher level + * from the source {@link Publisher}. + *

    + * Examples of such state include: + *

      + *
    • is the publisher a Mono? (unlocking Mono-specific behavior in the {@link SignalListener}
    • + *
    • resolution of NAME and TAGS on the source
    • + *
    • preparation of a common configuration, like a registry for metrics
    • + *
    + * + * @param the type of data emitted by the observed source {@link Publisher} + * @param the type of the publisher-level state that will be shared between all {@link SignalListener} created by this factory + * @author Simon Baslé + */ +public interface SignalListenerFactory { + + /** + * Create the {@code STATE} object for the given {@link Publisher}. This assumes this factory will only be used on + * that particular source, allowing all {@link SignalListener} created by this factory to inherit the common state. + * + * @param source the source {@link Publisher} this factory is attached to. + * @return the common state + */ + STATE initializePublisherState(Publisher source); + + /** + * Create a new {@link SignalListener} each time a new {@link org.reactivestreams.Subscriber} subscribes to the + * {@code source} {@link Publisher}. + *

    + * The {@code source} {@link Publisher} is the same as the one that triggered common state creation at assembly time in + * {@link #initializePublisherState(Publisher)}). Said common state is passed to this method as well, and so is the + * {@link ContextView} for the newly registered {@link reactor.core.CoreSubscriber}. + * + * @param source the source {@link Publisher} that is being subscribed to + * @param listenerContext the {@link ContextView} associated with the new subscriber + * @param publisherContext the common state initialized in {@link #initializePublisherState(Publisher)} + * @return a stateful {@link SignalListener} observing signals to and from the new subscriber + */ + SignalListener createListener(Publisher source, ContextView listenerContext, STATE publisherContext); + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/BaseSubscriber.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/BaseSubscriber.java (.../BaseSubscriber.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/BaseSubscriber.java (.../BaseSubscriber.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -131,7 +131,7 @@ * cancel). The hook is executed in addition to and after {@link #hookOnError(Throwable)}, * {@link #hookOnComplete()} and {@link #hookOnCancel()} hooks, even if these callbacks * fail. Defaults to doing nothing. A failure of the callback will be caught by - * {@link Operators#onErrorDropped(Throwable, reactor.util.context.Context)}. + * {@link Operators#onErrorDropped(Throwable, Context)}. * * @param type the type of termination event that triggered the hook * ({@link SignalType#ON_ERROR}, {@link SignalType#ON_COMPLETE} or @@ -251,4 +251,4 @@ public String toString() { return getClass().getSimpleName(); } -} +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/BlockingFirstSubscriber.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/BlockingFirstSubscriber.java (.../BlockingFirstSubscriber.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/BlockingFirstSubscriber.java (.../BlockingFirstSubscriber.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,8 @@ package reactor.core.publisher; +import reactor.util.context.Context; + /** * Blocks until the upstream signals its first value or completes. * @@ -24,6 +26,10 @@ */ final class BlockingFirstSubscriber extends BlockingSingleSubscriber { + public BlockingFirstSubscriber(Context context) { + super(context); + } + @Override public void onNext(T t) { if (value == null) { Index: 3rdParty_sources/reactor/reactor/core/publisher/BlockingIterable.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/BlockingIterable.java (.../BlockingIterable.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/BlockingIterable.java (.../BlockingIterable.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -54,9 +54,13 @@ final Supplier> queueSupplier; + final Supplier contextSupplier; + BlockingIterable(CorePublisher source, int batchSize, - Supplier> queueSupplier) { + Supplier> queueSupplier, + Supplier contextSupplier) { + this.contextSupplier = contextSupplier; if (batchSize <= 0) { throw new IllegalArgumentException("batchSize > 0 required but it was " + batchSize); } @@ -114,7 +118,7 @@ throw Exceptions.propagate(e); } - return new SubscriberIterator<>(q, batchSize); + return new SubscriberIterator<>(q, contextSupplier.get(), batchSize); } static final class SubscriberIterator @@ -130,6 +134,8 @@ final Condition condition; + final Context context; + long produced; volatile Subscription s; @@ -142,17 +148,18 @@ volatile boolean done; Throwable error; - SubscriberIterator(Queue queue, int batchSize) { + SubscriberIterator(Queue queue, Context context, int batchSize) { this.queue = queue; this.batchSize = batchSize; this.limit = Operators.unboundedOrLimit(batchSize); this.lock = new ReentrantLock(); this.condition = lock.newCondition(); + this.context = context; } @Override public Context currentContext() { - return Context.empty(); + return this.context; } @Override @@ -181,6 +188,7 @@ } catch (InterruptedException ex) { run(); + Thread.currentThread().interrupt(); throw Exceptions.propagate(ex); } finally { Index: 3rdParty_sources/reactor/reactor/core/publisher/BlockingLastSubscriber.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/BlockingLastSubscriber.java (.../BlockingLastSubscriber.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/BlockingLastSubscriber.java (.../BlockingLastSubscriber.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2015-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,8 @@ package reactor.core.publisher; +import reactor.util.context.Context; + /** * Blocks until the upstream signals its last value or completes. * @@ -24,6 +26,10 @@ */ final class BlockingLastSubscriber extends BlockingSingleSubscriber { + public BlockingLastSubscriber(Context context) { + super(context); + } + @Override public void onNext(T t) { value = t; Index: 3rdParty_sources/reactor/reactor/core/publisher/BlockingMonoSubscriber.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/BlockingMonoSubscriber.java (.../BlockingMonoSubscriber.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/BlockingMonoSubscriber.java (.../BlockingMonoSubscriber.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,8 @@ package reactor.core.publisher; +import reactor.util.context.Context; + /** * Blocks assuming the upstream is a Mono, until it signals its value or completes. * Compared to {@link BlockingFirstSubscriber}, this variant doesn't cancel the upstream @@ -26,6 +28,10 @@ */ final class BlockingMonoSubscriber extends BlockingSingleSubscriber { + public BlockingMonoSubscriber(Context context) { + super(context); + } + @Override public void onNext(T t) { if (value == null) { Index: 3rdParty_sources/reactor/reactor/core/publisher/BlockingOptionalMonoSubscriber.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/BlockingOptionalMonoSubscriber.java (.../BlockingOptionalMonoSubscriber.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/BlockingOptionalMonoSubscriber.java (.../BlockingOptionalMonoSubscriber.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,8 +45,11 @@ volatile boolean cancelled; - BlockingOptionalMonoSubscriber() { + final Context context; + + BlockingOptionalMonoSubscriber(Context context) { super(1); + this.context = context; } @Override @@ -80,7 +83,7 @@ @Override public Context currentContext() { - return Context.empty(); + return this.context; } @Override @@ -112,6 +115,8 @@ RuntimeException re = Exceptions.propagate(ex); //this is ok, as re is always a new non-singleton instance re.addSuppressed(new Exception("#blockOptional() has been interrupted")); + + Thread.currentThread().interrupt(); throw re; } } @@ -151,6 +156,8 @@ RuntimeException re = Exceptions.propagate(ex); //this is ok, as re is always a new non-singleton instance re.addSuppressed(new Exception("#blockOptional(timeout) has been interrupted")); + + Thread.currentThread().interrupt(); throw re; } } Index: 3rdParty_sources/reactor/reactor/core/publisher/BlockingSingleSubscriber.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/BlockingSingleSubscriber.java (.../BlockingSingleSubscriber.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/BlockingSingleSubscriber.java (.../BlockingSingleSubscriber.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,10 +37,13 @@ Subscription s; + final Context context; + volatile boolean cancelled; - BlockingSingleSubscriber() { + BlockingSingleSubscriber(Context context) { super(1); + this.context = context; } @Override @@ -58,7 +61,7 @@ @Override public Context currentContext() { - return Context.empty(); + return this.context; } @Override @@ -88,6 +91,7 @@ } catch (InterruptedException ex) { dispose(); + Thread.currentThread().interrupt(); throw Exceptions.propagate(ex); } } @@ -128,6 +132,7 @@ RuntimeException re = Exceptions.propagate(ex); //this is ok, as re is always a new non-singleton instance re.addSuppressed(new Exception("#block has been interrupted")); + Thread.currentThread().interrupt(); throw re; } } Index: 3rdParty_sources/reactor/reactor/core/publisher/ContextPropagation.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/ContextPropagation.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/ContextPropagation.java (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -0,0 +1,535 @@ +/* + * Copyright (c) 2022-2023 VMware Inc. or its affiliates, All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reactor.core.publisher; + +import java.util.AbstractQueue; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Queue; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.function.Supplier; + +import io.micrometer.context.ContextAccessor; +import io.micrometer.context.ContextRegistry; +import io.micrometer.context.ContextSnapshot; + +import io.micrometer.context.ContextSnapshotFactory; +import io.micrometer.context.ThreadLocalAccessor; +import reactor.core.observability.SignalListener; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; +import reactor.util.context.ContextView; + +/** + * Utility private class to detect if the context-propagation library is on the classpath and to offer + * ContextSnapshot support to {@link Flux} and {@link Mono}. + * + * @author Simon Baslé + */ +final class ContextPropagation { + + static final Function NO_OP = c -> c; + static final Function WITH_GLOBAL_REGISTRY_NO_PREDICATE; + + static ContextSnapshotFactory globalContextSnapshotFactory = null; + + static { + WITH_GLOBAL_REGISTRY_NO_PREDICATE = ContextPropagationSupport.isContextPropagationAvailable() ? + new ContextCaptureNoPredicate() : NO_OP; + + if (ContextPropagationSupport.isContextPropagation103Available()) { + globalContextSnapshotFactory = ContextSnapshotFactory.builder() + .clearMissing(false) + .build(); + } + } + + static void configureContextSnapshotFactory(boolean clearMissing) { + if (ContextPropagationSupport.isContextPropagation103OnClasspath) { + globalContextSnapshotFactory = ContextSnapshotFactory.builder() + .clearMissing(clearMissing) + .build(); + } + } + + @SuppressWarnings("unchecked") + static ContextSnapshot.Scope setThreadLocals(Object context) { + if (ContextPropagationSupport.isContextPropagation103OnClasspath) { + return globalContextSnapshotFactory.setThreadLocalsFrom(context); + } + else { + ContextRegistry registry = ContextRegistry.getInstance(); + ContextAccessor contextAccessor = registry.getContextAccessorForRead(context); + Map previousValues = null; + for (ThreadLocalAccessor threadLocalAccessor : registry.getThreadLocalAccessors()) { + Object key = threadLocalAccessor.key(); + Object value = ((ContextAccessor) contextAccessor).readValue((C) context, key); + previousValues = setThreadLocal(key, value, threadLocalAccessor, previousValues); + } + if (ContextPropagationSupport.isContextPropagation101Available()) { + return ReactorScopeImpl.from(previousValues, registry); + } + return ReactorScopeImpl100.from(previousValues, registry); + } + } + + @SuppressWarnings("unchecked") + private static Map setThreadLocal(Object key, @Nullable V value, + ThreadLocalAccessor accessor, @Nullable Map previousValues) { + + previousValues = (previousValues != null ? previousValues : new HashMap<>()); + previousValues.put(key, accessor.getValue()); + if (value != null) { + ((ThreadLocalAccessor) accessor).setValue(value); + } + else { + accessor.reset(); + } + return previousValues; + } + + static ContextSnapshot captureThreadLocals() { + if (ContextPropagationSupport.isContextPropagation103OnClasspath) { + return globalContextSnapshotFactory.captureAll(); + } + else { + return ContextSnapshot.captureAll(); + } + } + + public static Function scopePassingOnScheduleHook() { + return delegate -> { + ContextSnapshot contextSnapshot = captureThreadLocals(); + return contextSnapshot.wrap(delegate); + }; + } + + /** + * Create a support function that takes a snapshot of thread locals and merges them with the + * provided {@link Context}, resulting in a new {@link Context} which includes entries + * captured from threadLocals by the Context-Propagation API. + *

    + * This variant uses the implicit global {@code ContextRegistry} and captures from all + * available {@code ThreadLocalAccessors}. It is the same variant backing {@link Flux#contextCapture()} + * and {@link Mono#contextCapture()}. + * + * @return the {@link Context} augmented with captured entries + */ + static Function contextCapture() { + return WITH_GLOBAL_REGISTRY_NO_PREDICATE; + } + + static Context contextCaptureToEmpty() { + return contextCapture().apply(Context.empty()); + } + + /** + * When context-propagation library + * is available on the classpath, the provided {@link BiConsumer handler} will be + * called with {@link ThreadLocal} values restored from the provided {@link Context}. + * @param handler user provided handler + * @param contextSupplier supplies the potentially modified {@link Context} to + * restore {@link ThreadLocal} values from + * @return potentially wrapped {@link BiConsumer} or the original + * @param type of handled values + * @param the transformed type + */ + static BiConsumer> contextRestoreForHandle(BiConsumer> handler, Supplier contextSupplier) { + if (ContextPropagationSupport.shouldRestoreThreadLocalsInSomeOperators()) { + final Context ctx = contextSupplier.get(); + if (ctx.isEmpty()) { + return handler; + } + + if (ContextPropagationSupport.isContextPropagation103OnClasspath) { + return (v, sink) -> { + try (ContextSnapshot.Scope ignored = globalContextSnapshotFactory.setThreadLocalsFrom(ctx)) { + handler.accept(v, sink); + } + }; + } + else { + return (v, sink) -> { + try (ContextSnapshot.Scope ignored = ContextSnapshot.setAllThreadLocalsFrom(ctx)) { + handler.accept(v, sink); + } + }; + } + } + else { + return handler; + } + } + + /** + * When context-propagation library + * is available on the classpath, the provided {@link SignalListener} will be wrapped + * with another one that restores {@link ThreadLocal} values from the provided + * {@link Context}. + *

    Note, this is only applied to {@link FluxTap}, {@link FluxTapFuseable}, + * {@link MonoTap}, and {@link MonoTapFuseable}. The automatic propagation + * variants: {@link FluxTapRestoringThreadLocals} and + * {@link MonoTapRestoringThreadLocals} do not use this method. + * @param original the original {@link SignalListener} from the user + * @param contextSupplier supplies the potentially modified {@link Context} to + * restore {@link ThreadLocal} values from + * @return potentially wrapped {@link SignalListener} or the original + * @param type of handled values + */ + static SignalListener contextRestoreForTap(final SignalListener original, Supplier contextSupplier) { + if (!ContextPropagationSupport.isContextPropagationAvailable()) { + return original; + } + final Context ctx = contextSupplier.get(); + if (ctx.isEmpty()) { + return original; + } + + if (ContextPropagationSupport.isContextPropagation103OnClasspath) { + return new ContextRestore103SignalListener<>(original, ctx, globalContextSnapshotFactory); + } + else { + return new ContextRestoreSignalListener<>(original, ctx); + } + } + + //the SignalListener implementation can be tested independently with a test-specific ContextRegistry + static class ContextRestoreSignalListener implements SignalListener { + + final SignalListener original; + final ContextView context; + + public ContextRestoreSignalListener(SignalListener original, + ContextView context) { + this.original = original; + this.context = context; + } + + ContextSnapshot.Scope restoreThreadLocals() { + return ContextSnapshot.setAllThreadLocalsFrom(this.context); + } + + @Override + public final void doFirst() throws Throwable { + try (ContextSnapshot.Scope ignored = restoreThreadLocals()) { + original.doFirst(); + } + } + + @Override + public final void doFinally(SignalType terminationType) throws Throwable { + try (ContextSnapshot.Scope ignored = restoreThreadLocals()) { + original.doFinally(terminationType); + } + } + + @Override + public final void doOnSubscription() throws Throwable { + try (ContextSnapshot.Scope ignored = restoreThreadLocals()) { + original.doOnSubscription(); + } + } + + @Override + public final void doOnFusion(int negotiatedFusion) throws Throwable { + try (ContextSnapshot.Scope ignored = restoreThreadLocals()) { + original.doOnFusion(negotiatedFusion); + } + } + + @Override + public final void doOnRequest(long requested) throws Throwable { + try (ContextSnapshot.Scope ignored = restoreThreadLocals()) { + original.doOnRequest(requested); + } + } + + @Override + public final void doOnCancel() throws Throwable { + try (ContextSnapshot.Scope ignored = restoreThreadLocals()) { + original.doOnCancel(); + } + } + + @Override + public final void doOnNext(T value) throws Throwable { + try (ContextSnapshot.Scope ignored = restoreThreadLocals()) { + original.doOnNext(value); + } + } + + @Override + public final void doOnComplete() throws Throwable { + try (ContextSnapshot.Scope ignored = restoreThreadLocals()) { + original.doOnComplete(); + } + } + + @Override + public final void doOnError(Throwable error) throws Throwable { + try (ContextSnapshot.Scope ignored = restoreThreadLocals()) { + original.doOnError(error); + } + } + + @Override + public final void doAfterComplete() throws Throwable { + try (ContextSnapshot.Scope ignored = restoreThreadLocals()) { + original.doAfterComplete(); + } + } + + @Override + public final void doAfterError(Throwable error) throws Throwable { + try (ContextSnapshot.Scope ignored = restoreThreadLocals()) { + original.doAfterError(error); + } + } + + @Override + public final void doOnMalformedOnNext(T value) throws Throwable { + try (ContextSnapshot.Scope ignored = restoreThreadLocals()) { + original.doOnMalformedOnNext(value); + } + } + + @Override + public final void doOnMalformedOnError(Throwable error) throws Throwable { + try (ContextSnapshot.Scope ignored = restoreThreadLocals()) { + original.doOnMalformedOnError(error); + } + } + + @Override + public final void doOnMalformedOnComplete() throws Throwable { + try (ContextSnapshot.Scope ignored = restoreThreadLocals()) { + original.doOnMalformedOnComplete(); + } + } + + @Override + public final void handleListenerError(Throwable listenerError) { + try (ContextSnapshot.Scope ignored = restoreThreadLocals()) { + original.handleListenerError(listenerError); + } + } + + @Override + public final Context addToContext(Context originalContext) { + try (ContextSnapshot.Scope ignored = restoreThreadLocals()) { + return original.addToContext(originalContext); + } + } + } + + //the SignalListener implementation can be tested independently with a test-specific ContextRegistry + static final class ContextRestore103SignalListener extends ContextRestoreSignalListener { + + final ContextSnapshotFactory contextSnapshotFactory; + + public ContextRestore103SignalListener(SignalListener original, + ContextView context, ContextSnapshotFactory contextSnapshotFactory) { + super(original, context); + this.contextSnapshotFactory = contextSnapshotFactory; + } + + ContextSnapshot.Scope restoreThreadLocals() { + return contextSnapshotFactory.setThreadLocalsFrom(this.context); + } + } + + static final class ContextCaptureNoPredicate implements Function { + + @Override + public Context apply(Context context) { + return captureThreadLocals().updateContext(context); + } + } + + static final class ContextQueue extends AbstractQueue { + + static final String NOT_SUPPORTED_MESSAGE = "ContextQueue wrapper is intended " + + "for use with instances returned by Queues class. Iterator based " + + "methods are usually unsupported."; + + final Queue> envelopeQueue; + + boolean cleanOnNull; + boolean hasPrevious = false; + + Thread lastReader; + ContextSnapshot.Scope scope; + + @SuppressWarnings({"unchecked", "rawtypes"}) + ContextQueue(Queue queue) { + this.envelopeQueue = (Queue) queue; + } + + @Override + public int size() { + return envelopeQueue.size(); + } + + @Override + public boolean offer(T o) { + ContextSnapshot contextSnapshot = captureThreadLocals(); + return envelopeQueue.offer(new Envelope<>(o, contextSnapshot)); + } + + @Override + public T poll() { + Envelope envelope = envelopeQueue.poll(); + if (envelope == null) { + if (cleanOnNull && scope != null) { + // clear thread-locals if they were just restored + scope.close(); + } + cleanOnNull = true; + lastReader = Thread.currentThread(); + hasPrevious = false; + return null; + } + + + restoreTheContext(envelope); + hasPrevious = true; + return envelope.body; + } + + private void restoreTheContext(Envelope envelope) { + ContextSnapshot contextSnapshot = envelope.contextSnapshot; + // tries to read existing Thread for existing ThreadLocals + ContextSnapshot currentContextSnapshot = captureThreadLocals(); + if (!contextSnapshot.equals(currentContextSnapshot)) { + if (!hasPrevious || !Thread.currentThread().equals(this.lastReader)) { + // means context was restored form the envelope, + // thus it has to be cleared + cleanOnNull = true; + lastReader = Thread.currentThread(); + } + scope = contextSnapshot.setThreadLocals(); + } + else if (!hasPrevious || !Thread.currentThread().equals(this.lastReader)) { + // means same context was already available, no need to clean anything + cleanOnNull = false; + lastReader = Thread.currentThread(); + } + } + + @Override + @Nullable + public T peek() { + Envelope envelope = envelopeQueue.peek(); + return envelope == null ? null : envelope.body; + } + + @Override + public Iterator iterator() { + throw new UnsupportedOperationException(NOT_SUPPORTED_MESSAGE); + } + + } + + static class Envelope { + + final T body; + final ContextSnapshot contextSnapshot; + + Envelope(T body, ContextSnapshot contextSnapshot) { + this.body = body; + this.contextSnapshot = contextSnapshot; + } + } + + private static class ReactorScopeImpl implements ContextSnapshot.Scope { + + private final Map previousValues; + + private final ContextRegistry contextRegistry; + + private ReactorScopeImpl(Map previousValues, + ContextRegistry contextRegistry) { + this.previousValues = previousValues; + this.contextRegistry = contextRegistry; + } + + @Override + public void close() { + for (ThreadLocalAccessor accessor : this.contextRegistry.getThreadLocalAccessors()) { + if (this.previousValues.containsKey(accessor.key())) { + Object previousValue = this.previousValues.get(accessor.key()); + resetThreadLocalValue(accessor, previousValue); + } + } + } + + @SuppressWarnings("unchecked") + private void resetThreadLocalValue(ThreadLocalAccessor accessor, @Nullable V previousValue) { + if (previousValue != null) { + ((ThreadLocalAccessor) accessor).restore(previousValue); + } + else { + accessor.reset(); + } + } + + public static ContextSnapshot.Scope from(@Nullable Map previousValues, ContextRegistry registry) { + return (previousValues != null ? new ReactorScopeImpl(previousValues, registry) : () -> { + }); + } + } + + private static class ReactorScopeImpl100 implements ContextSnapshot.Scope { + + private final Map previousValues; + + private final ContextRegistry contextRegistry; + + private ReactorScopeImpl100(Map previousValues, + ContextRegistry contextRegistry) { + this.previousValues = previousValues; + this.contextRegistry = contextRegistry; + } + + @Override + public void close() { + for (ThreadLocalAccessor accessor : this.contextRegistry.getThreadLocalAccessors()) { + if (this.previousValues.containsKey(accessor.key())) { + Object previousValue = this.previousValues.get(accessor.key()); + resetThreadLocalValue(accessor, previousValue); + } + } + } + + @SuppressWarnings("unchecked") + private void resetThreadLocalValue(ThreadLocalAccessor accessor, @Nullable V previousValue) { + if (previousValue != null) { + ((ThreadLocalAccessor) accessor).setValue(previousValue); + } + else { + accessor.reset(); + } + } + + public static ContextSnapshot.Scope from(@Nullable Map previousValues, ContextRegistry registry) { + return (previousValues != null ? new ReactorScopeImpl100(previousValues, registry) : () -> { + }); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/ContextPropagationSupport.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/ContextPropagationSupport.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/ContextPropagationSupport.java (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2023 VMware Inc. or its affiliates, All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reactor.core.publisher; + +import reactor.util.Logger; +import reactor.util.Loggers; + +final class ContextPropagationSupport { + static final Logger LOGGER = Loggers.getLogger(ContextPropagationSupport.class); + + // Note: If reflection is used for this field, then the name of the field should end with 'Available'. + // The preprocessing for native-image support is in Spring Framework, and is a short term solution. + // The field should end with 'Available'. See org.springframework.aot.nativex.feature.PreComputeFieldFeature. + // Ultimately the long term solution should be provided by Reactor Core. + static final boolean isContextPropagationOnClasspath; + static final boolean isContextPropagation103OnClasspath; + static final boolean isContextPropagation101OnClasspath; + static boolean propagateContextToThreadLocals = false; + + static { + boolean contextPropagation = false; + boolean contextPropagation103 = false; + boolean contextPropagation101 = false; + try { + Class.forName("io.micrometer.context.ContextRegistry"); + contextPropagation = true; + Class.forName("io.micrometer.context.ThreadLocalAccessor").getDeclaredMethod("restore", Object.class); + contextPropagation101 = true; + Class.forName("io.micrometer.context.ContextSnapshotFactory"); + contextPropagation103 = true; + } catch (ClassNotFoundException notFound) { + } catch (NoSuchMethodException notFound) { + } catch (LinkageError linkageErr) { + } catch (Throwable err) { + LOGGER.error("Unexpected exception while detecting ContextPropagation feature." + + " The feature is considered disabled due to this:", err); + } + isContextPropagationOnClasspath = contextPropagation; + isContextPropagation101OnClasspath = contextPropagation101; + isContextPropagation103OnClasspath = contextPropagation103; + + if (isContextPropagationOnClasspath && !isContextPropagation103OnClasspath) { + LOGGER.warn("context-propagation version below 1.0.3 can cause memory leaks" + + " when working with scope-based ThreadLocalAccessors, please " + + "upgrade!"); + } + } + + /** + * Is Micrometer {@code context-propagation} API on the classpath? + * + * @return true if context-propagation is available at runtime, false otherwise + */ + static boolean isContextPropagationAvailable() { + return isContextPropagationOnClasspath; + } + + static boolean isContextPropagation101Available() { + return isContextPropagation101OnClasspath; + } + + static boolean isContextPropagation103Available() { + return isContextPropagation103OnClasspath; + } + + static boolean shouldPropagateContextToThreadLocals() { + return isContextPropagationOnClasspath && propagateContextToThreadLocals; + } + + static boolean shouldRestoreThreadLocalsInSomeOperators() { + return isContextPropagationOnClasspath && !propagateContextToThreadLocals; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/DelegateProcessor.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/DelegateProcessor.java (.../DelegateProcessor.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/DelegateProcessor.java (.../DelegateProcessor.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,8 +22,8 @@ import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; + import reactor.core.CoreSubscriber; -import reactor.core.Exceptions; import reactor.core.Scannable; import reactor.util.annotation.Nullable; import reactor.util.context.Context; Index: 3rdParty_sources/reactor/reactor/core/publisher/DirectInnerContainer.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/DirectInnerContainer.java (.../DirectInnerContainer.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/DirectInnerContainer.java (.../DirectInnerContainer.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,9 @@ * and {@link SinkManyBestEffort}. * * @author Simon Baslé + * @deprecated remove again once DirectProcessor is removed */ +@Deprecated interface DirectInnerContainer { /** Index: 3rdParty_sources/reactor/reactor/core/publisher/DirectProcessor.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/DirectProcessor.java (.../DirectProcessor.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/DirectProcessor.java (.../DirectProcessor.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -86,7 +86,7 @@ */ @Deprecated public final class DirectProcessor extends FluxProcessor - implements DirectInnerContainer { + implements DirectInnerContainer { /** * Create a new {@link DirectProcessor} @@ -106,7 +106,7 @@ private volatile DirectInner[] subscribers = SinkManyBestEffort.EMPTY; @SuppressWarnings("rawtypes") private static final AtomicReferenceFieldUpdaterSUBSCRIBERS = - AtomicReferenceFieldUpdater.newUpdater(DirectProcessor.class, DirectInner[].class, "subscribers"); + AtomicReferenceFieldUpdater.newUpdater(DirectProcessor.class, DirectInner[].class, "subscribers"); Throwable error; @@ -137,7 +137,7 @@ @Override public void onComplete() { //no particular error condition handling for onComplete - @SuppressWarnings("unused") Sinks.EmitResult emitResult = tryEmitComplete(); + @SuppressWarnings("unused") EmitResult emitResult = tryEmitComplete(); } private void emitComplete() { @@ -165,13 +165,13 @@ } private void emitError(Throwable error) { - Sinks.EmitResult result = tryEmitError(error); + EmitResult result = tryEmitError(error); if (result == EmitResult.FAIL_TERMINATED) { Operators.onErrorDroppedMulticast(error, subscribers); } } - private Sinks.EmitResult tryEmitError(Throwable t) { + private EmitResult tryEmitError(Throwable t) { Objects.requireNonNull(t, "t"); @SuppressWarnings("unchecked") @@ -185,7 +185,7 @@ for (DirectInner s : inners) { s.emitError(t); } - return Sinks.EmitResult.OK; + return EmitResult.OK; } private void emitNext(T value) { @@ -226,13 +226,13 @@ return EmitResult.FAIL_TERMINATED; } if (inners == SinkManyBestEffort.EMPTY) { - return Sinks.EmitResult.FAIL_ZERO_SUBSCRIBER; + return EmitResult.FAIL_ZERO_SUBSCRIBER; } for (DirectInner s : inners) { s.directEmitNext(t); } - return Sinks.EmitResult.OK; + return EmitResult.OK; } @Override @@ -356,4 +356,4 @@ return null; } -} +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/EmitterProcessor.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/EmitterProcessor.java (.../EmitterProcessor.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/EmitterProcessor.java (.../EmitterProcessor.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,14 +18,17 @@ import java.util.Objects; import java.util.Queue; +import java.util.concurrent.CancellationException; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import java.util.stream.Stream; +import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; import reactor.core.CoreSubscriber; +import reactor.core.Disposable; import reactor.core.Exceptions; import reactor.core.Fuseable; import reactor.core.Scannable; @@ -54,16 +57,18 @@ * @author Stephane Maldini * @deprecated To be removed in 3.5. Prefer clear cut usage of {@link Sinks} through * variations of {@link Sinks.MulticastSpec#onBackpressureBuffer() Sinks.many().multicast().onBackpressureBuffer()}. - * This processor was blocking in {@link EmitterProcessor#onNext(Object)}. - * This behaviour can be implemented with the {@link Sinks} API by calling + * If you really need the subscribe-to-upstream functionality of a {@link org.reactivestreams.Processor}, switch + * to {@link Sinks.ManyWithUpstream} with {@link Sinks#unsafe()} variants of {@link Sinks.RootSpec#manyWithUpstream() Sinks.unsafe().manyWithUpstream()}. + *

    This processor was blocking in {@link EmitterProcessor#onNext(Object)}. This behaviour can be implemented with the {@link Sinks} API by calling * {@link Sinks.Many#tryEmitNext(Object)} and retrying, e.g.: *

    {@code while (sink.tryEmitNext(v).hasFailed()) {
      *     LockSupport.parkNanos(10);
      * }
      * }
    */ @Deprecated -public final class EmitterProcessor extends FluxProcessor implements InternalManySink { +public final class EmitterProcessor extends FluxProcessor implements InternalManySink, + Sinks.ManyWithUpstream { @SuppressWarnings("rawtypes") static final FluxPublish.PubSubInner[] EMPTY = new FluxPublish.PublishInner[0]; @@ -150,6 +155,12 @@ FluxPublish.PubSubInner[].class, "subscribers"); + volatile EmitterDisposable upstreamDisposable; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater UPSTREAM_DISPOSABLE = + AtomicReferenceFieldUpdater.newUpdater(EmitterProcessor.class, EmitterDisposable.class, "upstreamDisposable"); + + @SuppressWarnings("unused") volatile int wip; @@ -192,7 +203,40 @@ return Operators.multiSubscribersContext(subscribers); } + + private boolean isDetached() { + return s == Operators.cancelledSubscription() && done && error instanceof CancellationException; + } + + private boolean detach() { + if (Operators.terminate(S, this)) { + done = true; + CancellationException detachException = new CancellationException("the ManyWithUpstream sink had a Subscription to an upstream which has been manually cancelled"); + if (ERROR.compareAndSet(EmitterProcessor.this, null, detachException)) { + Queue q = queue; + if (q != null) { + q.clear(); + } + for (FluxPublish.PubSubInner inner : terminate()) { + inner.actual.onError(detachException); + } + return true; + } + } + return false; + } + @Override + public Disposable subscribeTo(Publisher upstream) { + EmitterDisposable ed = new EmitterDisposable(this); + if (UPSTREAM_DISPOSABLE.compareAndSet(this, null, ed)) { + upstream.subscribe(this); + return ed; + } + throw new IllegalStateException("A Sinks.ManyWithUpstream must be subscribed to a source only once"); + } + + @Override public void subscribe(CoreSubscriber actual) { Objects.requireNonNull(actual, "subscribe"); EmitterInner inner = new EmitterInner<>(actual, this); @@ -252,7 +296,7 @@ return EmitResult.OK; } else { - return Sinks.EmitResult.FAIL_TERMINATED; + return EmitResult.FAIL_TERMINATED; } } @@ -268,7 +312,7 @@ @Override public EmitResult tryEmitNext(T t) { if (done) { - return Sinks.EmitResult.FAIL_TERMINATED; + return EmitResult.FAIL_TERMINATED; } Objects.requireNonNull(t, "onNext"); @@ -332,6 +376,8 @@ @Override public void onSubscribe(final Subscription s) { + //since the CoreSubscriber nature isn't exposed to the user, the only path to onSubscribe is + //already guarded by UPSTREAM_DISPOSABLE. just in case the publisher misbehaves we still use setOnce if (Operators.setOnce(S, this, s)) { if (s instanceof Fuseable.QueueSubscription) { @SuppressWarnings("unchecked") Fuseable.QueueSubscription f = @@ -616,8 +662,8 @@ q.clear(); } } + return; } - return; } } @@ -647,5 +693,31 @@ } } + static final class EmitterDisposable implements Disposable { + @Nullable + EmitterProcessor target; + + public EmitterDisposable(EmitterProcessor emitterProcessor) { + this.target = emitterProcessor; + } + + @Override + public boolean isDisposed() { + return target == null || target.isDetached(); + } + + @Override + public void dispose() { + EmitterProcessor t = target; + if (t == null) { + return; + } + if (t.detach() || t.isDetached()) { + target = null; + } + } + } + + } Index: 3rdParty_sources/reactor/reactor/core/publisher/Flux.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/Flux.java (.../Flux.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/Flux.java (.../Flux.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -44,6 +44,7 @@ import java.util.function.Supplier; import java.util.logging.Level; import java.util.stream.Collector; +import java.util.stream.IntStream; import java.util.stream.Stream; import io.micrometer.core.instrument.MeterRegistry; @@ -57,9 +58,9 @@ import reactor.core.Exceptions; import reactor.core.Fuseable; import reactor.core.Scannable; +import reactor.core.publisher.FluxOnAssembly.AssemblySnapshot; import reactor.core.publisher.FluxOnAssembly.CheckpointHeavySnapshot; import reactor.core.publisher.FluxOnAssembly.CheckpointLightSnapshot; -import reactor.core.publisher.FluxOnAssembly.AssemblySnapshot; import reactor.core.publisher.FluxSink.OverflowStrategy; import reactor.core.scheduler.Scheduler; import reactor.core.scheduler.Scheduler.Worker; @@ -78,6 +79,8 @@ import reactor.util.function.Tuple7; import reactor.util.function.Tuple8; import reactor.util.function.Tuples; +import reactor.core.observability.SignalListener; +import reactor.core.observability.SignalListenerFactory; import reactor.util.retry.Retry; /** @@ -394,7 +397,7 @@ int prefetch, Function combinator) { - return onAssembly(new FluxCombineLatest(sources, + return onAssembly(new FluxCombineLatest<>(sources, combinator, Queues.get(prefetch), prefetch)); } @@ -685,7 +688,7 @@ * available backpressure modes * @param emitter Consume the {@link FluxSink} provided per-subscriber by Reactor to generate signals. * @return a {@link Flux} - * @see #push(Consumer, reactor.core.publisher.FluxSink.OverflowStrategy) + * @see #push(Consumer, OverflowStrategy) */ public static Flux create(Consumer> emitter, OverflowStrategy backpressure) { return onAssembly(new FluxCreate<>(emitter, backpressure, FluxCreate.CreateMode.PUSH_PULL)); @@ -738,7 +741,7 @@ /** * Programmatically create a {@link Flux} with the capability of emitting multiple * elements from a single-threaded producer through the {@link FluxSink} API. For - * a multi-threaded capable alternative, see {@link #create(Consumer, reactor.core.publisher.FluxSink.OverflowStrategy)}. + * a multi-threaded capable alternative, see {@link #create(Consumer, OverflowStrategy)}. *

    * *

    @@ -777,7 +780,7 @@ * available backpressure modes * @param emitter Consume the {@link FluxSink} provided per-subscriber by Reactor to generate signals. * @return a {@link Flux} - * @see #create(Consumer, reactor.core.publisher.FluxSink.OverflowStrategy) + * @see #create(Consumer, OverflowStrategy) */ public static Flux push(Consumer> emitter, OverflowStrategy backpressure) { return onAssembly(new FluxCreate<>(emitter, backpressure, FluxCreate.CreateMode.PUSH_ONLY)); @@ -808,28 +811,6 @@ * resulting {@link Flux}, so the actual source instantiation is deferred until each * subscribe and the {@link Function} can create a subscriber-specific instance. * This operator behaves the same way as {@link #defer(Supplier)}, - * but accepts a {@link Function} that will receive the current {@link Context} as an argument. - * If the supplier doesn't generate a new instance however, this operator will - * effectively behave like {@link #from(Publisher)}. - * - *

    - * - * - * @param contextualPublisherFactory the {@link Publisher} {@link Function} to call on subscribe - * @param the type of values passing through the {@link Flux} - * @return a deferred {@link Flux} deriving actual {@link Flux} from context values for each subscription - * @deprecated use {@link #deferContextual(Function)} - */ - @Deprecated - public static Flux deferWithContext(Function> contextualPublisherFactory) { - return deferContextual(view -> contextualPublisherFactory.apply(Context.of(view))); - } - - /** - * Lazily supply a {@link Publisher} every time a {@link Subscription} is made on the - * resulting {@link Flux}, so the actual source instantiation is deferred until each - * subscribe and the {@link Function} can create a subscriber-specific instance. - * This operator behaves the same way as {@link #defer(Supplier)}, * but accepts a {@link Function} that will receive the current {@link ContextView} as an argument. * If the function doesn't generate a new instance however, this operator will * effectively behave like {@link #from(Publisher)}. @@ -1518,6 +1499,122 @@ /** * Merge data from provided {@link Publisher} sequences into an ordered merged sequence, + * by picking the smallest values from each source (as defined by their natural order) as they arrive. + * This is not a {@link #sort()}, as it doesn't consider the whole of each sequences. Unlike mergeComparing, + * this operator does not wait for a value from each source to arrive either. + *

    + * While this operator does retrieve at most one value from each source, it only compares values when two or more + * sources emit at the same time. In that case it picks the smallest of these competing values and continues doing so + * as long as there is demand. It is therefore best suited for asynchronous sources where you do not want to wait + * for a value from each source before emitting a value downstream. + *

    + * + * + * @param sources {@link Publisher} sources of {@link Comparable} to merge + * @param a {@link Comparable} merged type that has a {@link Comparator#naturalOrder() natural order} + * @return a merged {@link Flux} that compares the latest available value from each source, publishing the + * smallest value and replenishing the source that produced it. + */ + @SafeVarargs + public static > Flux mergePriority(Publisher... sources) { + return mergePriority(Queues.SMALL_BUFFER_SIZE, Comparator.naturalOrder(), sources); + } + + /** + * Merge data from provided {@link Publisher} sequences into an ordered merged sequence, + * by picking the smallest values from each source (as defined by the provided + * {@link Comparator}) as they arrive. This is not a {@link #sort(Comparator)}, as it doesn't consider + * the whole of each sequences. Unlike mergeComparing, this operator does not wait for a value from each + * source to arrive either. + *

    + * While this operator does retrieve at most one value from each source, it only compares values when two or more + * sources emit at the same time. In that case it picks the smallest of these competing values and continues doing so + * as long as there is demand. It is therefore best suited for asynchronous sources where you do not want to wait + * for a value from each source before emitting a value downstream. + *

    + * + * + * @param comparator the {@link Comparator} to use to find the smallest value + * @param sources {@link Publisher} sources to merge + * @param the merged type + * @return a merged {@link Flux} that compares the latest available value from each source, publishing the + * smallest value and replenishing the source that produced it. + */ + @SafeVarargs + public static Flux mergePriority(Comparator comparator, Publisher... sources) { + return mergePriority(Queues.SMALL_BUFFER_SIZE, comparator, sources); + } + + /** + * Merge data from provided {@link Publisher} sequences into an ordered merged sequence, + * by picking the smallest values from each source (as defined by the provided + * {@link Comparator}) as they arrive. This is not a {@link #sort(Comparator)}, as it doesn't consider + * the whole of each sequences. Unlike mergeComparing, this operator does not wait for a value from each + * source to arrive either. + *

    + * While this operator does retrieve at most one value from each source, it only compares values when two or more + * sources emit at the same time. In that case it picks the smallest of these competing values and continues doing so + * as long as there is demand. It is therefore best suited for asynchronous sources where you do not want to wait + * for a value from each source before emitting a value downstream. + *

    + * + * + * @param prefetch the number of elements to prefetch from each source (avoiding too + * many small requests to the source when picking) + * @param comparator the {@link Comparator} to use to find the smallest value + * @param sources {@link Publisher} sources to merge + * @param the merged type + * @return a merged {@link Flux} that compares the latest available value from each source, publishing the + * smallest value and replenishing the source that produced it. + */ + @SafeVarargs + public static Flux mergePriority(int prefetch, Comparator comparator, Publisher... sources) { + if (sources.length == 0) { + return empty(); + } + if (sources.length == 1) { + return from(sources[0]); + } + return onAssembly(new FluxMergeComparing<>(prefetch, comparator, false, false, sources)); + } + + /** + * Merge data from provided {@link Publisher} sequences into an ordered merged sequence, + * by picking the smallest values from each source (as defined by the provided + * {@link Comparator}) as they arrive. This is not a {@link #sort(Comparator)}, as it doesn't consider + * the whole of each sequences. Unlike mergeComparing, this operator does not wait for a value from each + * source to arrive either. + *

    + * While this operator does retrieve at most one value from each source, it only compares values when two or more + * sources emit at the same time. In that case it picks the smallest of these competing values and continues doing so + * as long as there is demand. It is therefore best suited for asynchronous sources where you do not want to wait + * for a value from each source before emitting a value downstream. + *

    + * Note that it is delaying errors until all data is consumed. + *

    + * + * + * @param prefetch the number of elements to prefetch from each source (avoiding too + * many small requests to the source when picking) + * @param comparator the {@link Comparator} to use to find the smallest value + * @param sources {@link Publisher} sources to merge + * @param the merged type + * @return a merged {@link Flux} that compares the latest available value from each source, publishing the + * smallest value and replenishing the source that produced it. + */ + @SafeVarargs + public static Flux mergePriorityDelayError(int prefetch, Comparator comparator, Publisher... sources) { + if (sources.length == 0) { + return empty(); + } + if (sources.length == 1) { + return from(sources[0]); + } + return onAssembly(new FluxMergeComparing<>(prefetch, comparator, true, false, sources)); + } + + /** + * Merge data from provided {@link Publisher} sequences into an ordered merged sequence, * by picking the smallest values from each source (as defined by their natural order). * This is not a {@link #sort()}, as it doesn't consider the whole of each sequences. *

    @@ -1584,7 +1681,7 @@ if (sources.length == 1) { return from(sources[0]); } - return onAssembly(new FluxMergeComparing<>(prefetch, comparator, false, sources)); + return onAssembly(new FluxMergeComparing<>(prefetch, comparator, false, true, sources)); } /** @@ -1616,7 +1713,7 @@ if (sources.length == 1) { return from(sources[0]); } - return onAssembly(new FluxMergeComparing<>(prefetch, comparator, true, sources)); + return onAssembly(new FluxMergeComparing<>(prefetch, comparator, true, true, sources)); } /** @@ -1706,7 +1803,7 @@ if (sources.length == 1) { return from(sources[0]); } - return onAssembly(new FluxMergeComparing<>(prefetch, comparator, true, sources)); + return onAssembly(new FluxMergeComparing<>(prefetch, comparator, true, true, sources)); } /** @@ -1919,26 +2016,31 @@ /** * Creates a {@link Flux} that mirrors the most recently emitted {@link Publisher}, - * forwarding its data until a new {@link Publisher} comes in in the source. + * forwarding its data until a new {@link Publisher} comes in the source. *

    * The resulting {@link Flux} will complete once there are no new {@link Publisher} in * the source (source has completed) and the last mirrored {@link Publisher} has also * completed. *

    * + *

    + * This operator requests the {@code mergedPublishers} source for an unbounded amount of inner publishers, + * but doesn't request each inner {@link Publisher} unless the downstream has made + * a corresponding request (no prefetch on publishers emitted by {@code mergedPublishers}). * * @param mergedPublishers The {@link Publisher} of {@link Publisher} to switch on and mirror. * @param the produced type * - * @return a {@link FluxProcessor} accepting publishers and producing T + * @return a {@link SinkManyAbstractBase} accepting publishers and producing T */ public static Flux switchOnNext(Publisher> mergedPublishers) { - return switchOnNext(mergedPublishers, Queues.XS_BUFFER_SIZE); + return onAssembly(new FluxSwitchMapNoPrefetch<>(from(mergedPublishers), + identityFunction())); } /** * Creates a {@link Flux} that mirrors the most recently emitted {@link Publisher}, - * forwarding its data until a new {@link Publisher} comes in in the source. + * forwarding its data until a new {@link Publisher} comes in the source. *

    * The resulting {@link Flux} will complete once there are no new {@link Publisher} in * the source (source has completed) and the last mirrored {@link Publisher} has also @@ -1950,7 +2052,7 @@ * @param prefetch the inner source request size * @param the produced type * - * @return a {@link FluxProcessor} accepting publishers and producing T + * @return a {@link SinkManyAbstractBase} accepting publishers and producing T * * @deprecated to be removed in 3.6.0 at the earliest. In 3.5.0, you should replace * calls with prefetch=0 with calls to switchOnNext(mergedPublishers), as the default @@ -2412,10 +2514,10 @@ int prefetch, final Function combinator) { - return onAssembly(new FluxZip(sources, - combinator, - Queues.get(prefetch), - prefetch)); + return onAssembly(new FluxZip<>(sources, + combinator, + Queues.get(prefetch), + prefetch)); } /** @@ -2595,7 +2697,9 @@ */ @Nullable public final T blockFirst() { - BlockingFirstSubscriber subscriber = new BlockingFirstSubscriber<>(); + Context context = ContextPropagationSupport.shouldPropagateContextToThreadLocals() + ? ContextPropagation.contextCaptureToEmpty() : Context.empty(); + BlockingFirstSubscriber subscriber = new BlockingFirstSubscriber<>(context); subscribe((Subscriber) subscriber); return subscriber.blockingGet(); } @@ -2618,7 +2722,9 @@ */ @Nullable public final T blockFirst(Duration timeout) { - BlockingFirstSubscriber subscriber = new BlockingFirstSubscriber<>(); + Context context = ContextPropagationSupport.shouldPropagateContextToThreadLocals() + ? ContextPropagation.contextCaptureToEmpty() : Context.empty(); + BlockingFirstSubscriber subscriber = new BlockingFirstSubscriber<>(context); subscribe((Subscriber) subscriber); return subscriber.blockingGet(timeout.toNanos(), TimeUnit.NANOSECONDS); } @@ -2640,7 +2746,9 @@ */ @Nullable public final T blockLast() { - BlockingLastSubscriber subscriber = new BlockingLastSubscriber<>(); + Context context = ContextPropagationSupport.shouldPropagateContextToThreadLocals() + ? ContextPropagation.contextCaptureToEmpty() : Context.empty(); + BlockingLastSubscriber subscriber = new BlockingLastSubscriber<>(context); subscribe((Subscriber) subscriber); return subscriber.blockingGet(); } @@ -2664,7 +2772,9 @@ */ @Nullable public final T blockLast(Duration timeout) { - BlockingLastSubscriber subscriber = new BlockingLastSubscriber<>(); + Context context = ContextPropagationSupport.shouldPropagateContextToThreadLocals() + ? ContextPropagation.contextCaptureToEmpty() : Context.empty(); + BlockingLastSubscriber subscriber = new BlockingLastSubscriber<>(context); subscribe((Subscriber) subscriber); return subscriber.blockingGet(timeout.toNanos(), TimeUnit.NANOSECONDS); } @@ -3001,10 +3111,104 @@ */ public final > Flux bufferTimeout(int maxSize, Duration maxTime, Scheduler timer, Supplier bufferSupplier) { - return onAssembly(new FluxBufferTimeout<>(this, maxSize, maxTime.toNanos(), TimeUnit.NANOSECONDS, timer, bufferSupplier)); + return onAssembly(new FluxBufferTimeout<>(this, maxSize, maxTime.toNanos(), TimeUnit.NANOSECONDS, timer, bufferSupplier, + false)); } /** + * Collect incoming values into multiple {@link List} buffers that will be emitted + * by the returned {@link Flux} each time the buffer reaches a maximum size OR the + * maxTime {@link Duration} elapses. + *

    + * + * + *

    Discard Support: This operator discards the currently open buffer upon cancellation or error triggered by a data signal. + * + * @param maxSize the max collected size + * @param maxTime the timeout enforcing the release of a partial buffer + * @param fairBackpressure If {@code true}, prefetches {@code maxSize * 4} from upstream and replenishes the buffer when the downstream demand is satisfactory. + * When {@code false}, no prefetching takes place and a single buffer is always ready to be pushed downstream. + * + * @return a microbatched {@link Flux} of {@link List} delimited by given size or a given period timeout + */ + public final Flux> bufferTimeout(int maxSize, + Duration maxTime, boolean fairBackpressure) { + return bufferTimeout(maxSize, maxTime, Schedulers.parallel(), + listSupplier(), fairBackpressure); + } + + /** + * Collect incoming values into multiple {@link List} buffers that will be emitted + * by the returned {@link Flux} each time the buffer reaches a maximum size OR the + * maxTime {@link Duration} elapses, as measured on the provided {@link Scheduler}. + *

    + * + * + *

    Discard Support: This operator discards the currently open buffer upon cancellation or error triggered by a data signal. + * + * @param maxSize the max collected size + * @param maxTime the timeout enforcing the release of a partial buffer + * @param timer a time-capable {@link Scheduler} instance to run on + * @param fairBackpressure If {@code true}, prefetches {@code maxSize * 4} from upstream and replenishes the buffer when the downstream demand is satisfactory. + * When {@code false}, no prefetching takes place and a single buffer is always ready to be pushed downstream. + * + * @return a microbatched {@link Flux} of {@link List} delimited by given size or a given period timeout + */ + public final Flux> bufferTimeout(int maxSize, Duration maxTime, + Scheduler timer, boolean fairBackpressure) { + return bufferTimeout(maxSize, maxTime, timer, listSupplier(), fairBackpressure); + } + + /** + * Collect incoming values into multiple user-defined {@link Collection} buffers that + * will be emitted by the returned {@link Flux} each time the buffer reaches a maximum + * size OR the maxTime {@link Duration} elapses. + *

    + * + * + *

    Discard Support: This operator discards the currently open buffer upon cancellation or error triggered by a data signal. + * + * @param maxSize the max collected size + * @param maxTime the timeout enforcing the release of a partial buffer + * @param bufferSupplier a {@link Supplier} of the concrete {@link Collection} to use for each buffer + * @param the {@link Collection} buffer type + * @param fairBackpressure If {@code true}, prefetches {@code maxSize * 4} from upstream and replenishes the buffer when the downstream demand is satisfactory. + * When {@code false}, no prefetching takes place and a single buffer is always ready to be pushed downstream. + * + * @return a microbatched {@link Flux} of {@link Collection} delimited by given size or a given period timeout + */ + public final > Flux bufferTimeout(int maxSize, Duration maxTime, + Supplier bufferSupplier, boolean fairBackpressure) { + return bufferTimeout(maxSize, maxTime, Schedulers.parallel(), bufferSupplier, + fairBackpressure); + } + + /** + * Collect incoming values into multiple user-defined {@link Collection} buffers that + * will be emitted by the returned {@link Flux} each time the buffer reaches a maximum + * size OR the maxTime {@link Duration} elapses, as measured on the provided {@link Scheduler}. + *

    + * + * + *

    Discard Support: This operator discards the currently open buffer upon cancellation or error triggered by a data signal. + * + * @param maxSize the max collected size + * @param maxTime the timeout enforcing the release of a partial buffer + * @param timer a time-capable {@link Scheduler} instance to run on + * @param bufferSupplier a {@link Supplier} of the concrete {@link Collection} to use for each buffer + * @param the {@link Collection} buffer type + * @param fairBackpressure If {@code true}, prefetches {@code maxSize * 4} from upstream and replenishes the buffer when the downstream demand is satisfactory. + * When {@code false}, no prefetching takes place and a single buffer is always ready to be pushed downstream. + * + * @return a microbatched {@link Flux} of {@link Collection} delimited by given size or a given period timeout + */ + public final > Flux bufferTimeout(int maxSize, Duration maxTime, + Scheduler timer, Supplier bufferSupplier, boolean fairBackpressure) { + return onAssembly(new FluxBufferTimeout<>(this, maxSize, maxTime.toNanos(), TimeUnit.NANOSECONDS, timer, bufferSupplier, + fairBackpressure)); + } + + /** * Collect incoming values into multiple {@link List} buffers that will be emitted by * the resulting {@link Flux} each time the given predicate returns true. Note that * the element that triggers the predicate to return true (and thus closes a buffer) @@ -3065,7 +3269,7 @@ * * @return a microbatched {@link Flux} of {@link List} */ - public final Flux> bufferUntilChanged() { + public final Flux> bufferUntilChanged() { return bufferUntilChanged(identityFunction()); } @@ -3339,7 +3543,7 @@ * creation. *

    * It should be placed towards the end of the reactive chain, as errors - * triggered downstream of it cannot be observed and augmented with the backtrace. + * triggered downstream of it cannot be observed and augmented with the traceback. *

    * The traceback is attached to the error as a {@link Throwable#getSuppressed() suppressed exception}. * As such, if the error is a {@link Exceptions#isMultiple(Throwable) composite one}, the traceback @@ -3454,7 +3658,7 @@ *

    * * - *

    Discard Support: This operator discards the intermediate container (see {@link Collector#supplier()} upon + *

    Discard Support: This operator discards the intermediate container (see {@link Collector#supplier()}) upon * cancellation, error or exception while applying the {@link Collector#finisher()}. Either the container type * is a {@link Collection} (in which case individual elements are discarded) or not (in which case the entire * container is discarded). In case the accumulator {@link BiConsumer} of the collector fails to accumulate @@ -3733,7 +3937,6 @@ * * @return a {@link Mono} of a sorted {@link List} of all values from this {@link Flux} */ - @SuppressWarnings({ "unchecked", "rawtypes" }) public final Mono> collectSortedList(@Nullable Comparator comparator) { return collectList().doOnNext(list -> { // Note: this assumes the list emitted by buffer() is mutable @@ -3760,6 +3963,8 @@ * *

    * Errors will immediately short circuit current concat backlog. + * Note that no prefetching is done on the source, which gets requested only if there + * is downstream demand. * *

    * @@ -3771,9 +3976,8 @@ * * @return a concatenated {@link Flux} */ - public final Flux concatMap(Function> - mapper) { - return concatMap(mapper, Queues.XS_BUFFER_SIZE); + public final Flux concatMap(Function> mapper) { + return onAssembly(new FluxConcatMapNoPrefetch<>(this, mapper, FluxConcatMap.ErrorMode.IMMEDIATE)); } /** @@ -3795,21 +3999,21 @@ * *

    * Errors will immediately short circuit current concat backlog. The prefetch argument - * allows to give an arbitrary prefetch size to the upstream source. + * allows to give an arbitrary prefetch size to the upstream source, or to disable + * prefetching with {@code 0}. * *

    * * *

    Discard Support: This operator discards elements it internally queued for backpressure upon cancellation. * * @param mapper the function to transform this sequence of T into concatenated sequences of V - * @param prefetch the number of values to prefetch from upstream source (set it to 0 if you don't want it to prefetch) + * @param prefetch the number of values to prefetch from upstream source, or {@code 0} to disable prefetching * @param the produced concatenated type * * @return a concatenated {@link Flux} */ - public final Flux concatMap(Function> - mapper, int prefetch) { + public final Flux concatMap(Function> mapper, int prefetch) { if (prefetch == 0) { return onAssembly(new FluxConcatMapNoPrefetch<>(this, mapper, FluxConcatMap.ErrorMode.IMMEDIATE)); } @@ -3836,8 +4040,10 @@ * *

    * Errors in the individual publishers will be delayed at the end of the whole concat - * sequence (possibly getting combined into a {@link Exceptions#isMultiple(Throwable) composite} + * sequence (possibly getting combined into a {@link Exceptions#isMultiple(Throwable) composite}) * if several sources error. + * Note that no prefetching is done on the source, which gets requested only if there + * is downstream demand. * *

    * @@ -3851,7 +4057,7 @@ * */ public final Flux concatMapDelayError(Function> mapper) { - return concatMapDelayError(mapper, Queues.XS_BUFFER_SIZE); + return concatMapDelayError(mapper, 0); } /** @@ -3873,24 +4079,24 @@ * *

    * Errors in the individual publishers will be delayed at the end of the whole concat - * sequence (possibly getting combined into a {@link Exceptions#isMultiple(Throwable) composite} + * sequence (possibly getting combined into a {@link Exceptions#isMultiple(Throwable) composite}) * if several sources error. - * The prefetch argument allows to give an arbitrary prefetch size to the upstream source. + * The prefetch argument allows to give an arbitrary prefetch size to the upstream source, + * or to disable prefetching with {@code 0}. * *

    * * *

    Discard Support: This operator discards elements it internally queued for backpressure upon cancellation. * * @param mapper the function to transform this sequence of T into concatenated sequences of V - * @param prefetch the number of values to prefetch from upstream source + * @param prefetch the number of values to prefetch from upstream source, or {@code 0} to disable prefetching * @param the produced concatenated type * * @return a concatenated {@link Flux} * */ - public final Flux concatMapDelayError(Function> mapper, int prefetch) { + public final Flux concatMapDelayError(Function> mapper, int prefetch) { return concatMapDelayError(mapper, true, prefetch); } @@ -3914,7 +4120,8 @@ *

    * Errors in the individual publishers will be delayed after the current concat * backlog if delayUntilEnd is false or after all sources if delayUntilEnd is true. - * The prefetch argument allows to give an arbitrary prefetch size to the upstream source. + * The prefetch argument allows to give an arbitrary prefetch size to the upstream source, + * or to disable prefetching with {@code 0}. * *

    * @@ -3924,14 +4131,14 @@ * @param mapper the function to transform this sequence of T into concatenated sequences of V * @param delayUntilEnd delay error until all sources have been consumed instead of * after the current source - * @param prefetch the number of values to prefetch from upstream source + * @param prefetch the number of values to prefetch from upstream source, or {@code 0} to disable prefetching * @param the produced concatenated type * * @return a concatenated {@link Flux} * */ - public final Flux concatMapDelayError(Function> mapper, boolean delayUntilEnd, int prefetch) { + public final Flux concatMapDelayError(Function> mapper, + boolean delayUntilEnd, int prefetch) { FluxConcatMap.ErrorMode errorMode = delayUntilEnd ? FluxConcatMap.ErrorMode.END : FluxConcatMap.ErrorMode.BOUNDARY; if (prefetch == 0) { return onAssembly(new FluxConcatMapNoPrefetch<>(this, mapper, errorMode)); @@ -4032,7 +4239,6 @@ */ public final Flux concatWith(Publisher other) { if (this instanceof FluxConcatArray) { - @SuppressWarnings({ "unchecked" }) FluxConcatArray fluxConcatArray = (FluxConcatArray) this; return fluxConcatArray.concatAdditionalSourceLast(other); @@ -4041,6 +4247,36 @@ } /** + * If context-propagation library + * is on the classpath, this is a convenience shortcut to capture thread local values during the + * subscription phase and put them in the {@link Context} that is visible upstream of this operator. + *

    + * As a result this operator should generally be used as close as possible to the end of + * the chain / subscription point. + *

    + * If the {@link ContextView} visible upstream is not empty, a small subset of operators will automatically + * restore the context snapshot ({@link #handle(BiConsumer) handle}, {@link #tap(SignalListenerFactory) tap}). + * If context-propagation is not available at runtime, this operator simply returns the current {@link Flux} + * instance. + * + * @return a new {@link Flux} where context-propagation API has been used to capture entries and + * inject them into the {@link Context} + * @see #handle(BiConsumer) + * @see #tap(SignalListenerFactory) + */ + public final Flux contextCapture() { + if (!ContextPropagationSupport.isContextPropagationAvailable()) { + return this; + } + if (ContextPropagationSupport.propagateContextToThreadLocals) { + return onAssembly(new FluxContextWriteRestoringThreadLocals<>( + this, ContextPropagation.contextCapture() + )); + } + return onAssembly(new FluxContextWrite<>(this, ContextPropagation.contextCapture())); + } + + /** * Enrich the {@link Context} visible from downstream for the benefit of upstream * operators, by making all values from the provided {@link ContextView} visible on top * of pairs from downstream. @@ -4083,6 +4319,11 @@ * @see Context */ public final Flux contextWrite(Function contextModifier) { + if (ContextPropagationSupport.shouldPropagateContextToThreadLocals()) { + return onAssembly(new FluxContextWriteRestoringThreadLocals<>( + this, contextModifier + )); + } return onAssembly(new FluxContextWrite<>(this, contextModifier)); } @@ -4131,7 +4372,7 @@ /** * Delay each of this {@link Flux} elements ({@link Subscriber#onNext} signals) - * by a given {@link Duration}. Signals are delayed and continue on an user-specified + * by a given {@link Duration}. Signals are delayed and continue on a user-specified * {@link Scheduler}, but empty sequences or immediate error signals are not delayed. * *

    @@ -4170,7 +4411,7 @@ * * @param delay {@link Duration} to shift the sequence by * - * @return an shifted {@link Flux} emitting at the same frequency as the source + * @return a shifted {@link Flux} emitting at the same frequency as the source */ public final Flux delaySequence(Duration delay) { return delaySequence(delay, Schedulers.parallel()); @@ -4182,7 +4423,7 @@ * as they are emitted, always resulting in the delay between two elements being * the same as in the source (only the first element is visibly delayed from the * previous event, that is the subscription). - * Signals are delayed and continue on an user-specified {@link Scheduler}, but empty + * Signals are delayed and continue on a user-specified {@link Scheduler}, but empty * sequences or immediate error signals are not delayed. *

    * With this operator, a source emitting at 10Hz with a delaySequence {@link Duration} @@ -4202,7 +4443,7 @@ * @param delay {@link Duration} to shift the sequence by * @param timer a time-capable {@link Scheduler} instance to delay signals on * - * @return an shifted {@link Flux} emitting at the same frequency as the source + * @return a shifted {@link Flux} emitting at the same frequency as the source */ public final Flux delaySequence(Duration delay, Scheduler timer) { return onAssembly(new FluxDelaySequence<>(this, delay, timer)); @@ -4559,7 +4800,7 @@ * @return a {@link Flux} that cleans up matching elements that get discarded upstream of it. */ public final Flux doOnDiscard(final Class type, final Consumer discardHook) { - return subscriberContext(Operators.discardLocalAdapter(type, discardHook)); + return contextWrite(Operators.discardLocalAdapter(type, discardHook)); } /** @@ -4809,14 +5050,11 @@ */ public final Flux doFinally(Consumer onFinally) { Objects.requireNonNull(onFinally, "onFinally"); - if (this instanceof Fuseable) { - return onAssembly(new FluxDoFinallyFuseable<>(this, onFinally)); - } return onAssembly(new FluxDoFinally<>(this, onFinally)); } /** - * Map this {@link Flux} into {@link reactor.util.function.Tuple2 Tuple2<Long, T>} + * Map this {@link Flux} into {@link Tuple2 Tuple2<Long, T>} * of timemillis and source data. The timemillis corresponds to the elapsed time * between each signal as measured by the {@link Schedulers#parallel() parallel} scheduler. * First duration is measured between the subscription and the first element. @@ -4833,7 +5071,7 @@ } /** - * Map this {@link Flux} into {@link reactor.util.function.Tuple2 Tuple2<Long, T>} + * Map this {@link Flux} into {@link Tuple2 Tuple2<Long, T>} * of timemillis and source data. The timemillis corresponds to the elapsed time * between each signal as measured by the provided {@link Scheduler}. * First duration is measured between the subscription and the first element. @@ -4969,7 +5207,7 @@ *

    * That is: emit the values from this {@link Flux} first, then expand each at a first level of * recursion and emit all of the resulting values, then expand all of these at a second - * level and so on.. + * level and so on. *

    * For example, given the hierarchical structure *

    @@ -4996,7 +5234,7 @@
     	 * @param capacityHint a capacity hint to prepare the inner queues to accommodate n
     	 * elements per level of recursion.
     	 *
    -	 * @return an breadth-first expanded {@link Flux}
    +	 * @return a breadth-first expanded {@link Flux}
     	 */
     	public final Flux expand(Function> expander,
     			int capacityHint) {
    @@ -5034,7 +5272,7 @@
     	 * @param expander the {@link Function} applied at each level of recursion to expand
     	 * values into a {@link Publisher}, producing a graph.
     	 *
    -	 * @return an breadth-first expanded {@link Flux}
    +	 * @return a breadth-first expanded {@link Flux}
     	 */
     	public final Flux expand(Function> expander) {
     		return expand(expander, Queues.SMALL_BUFFER_SIZE);
    @@ -5788,11 +6026,14 @@
     	 * 

    Error Mode Support: This operator supports {@link #onErrorContinue(BiConsumer) resuming on errors} (including when * fusion is enabled) when the {@link BiConsumer} throws an exception or if an error is signaled explicitly via * {@link SynchronousSink#error(Throwable)}. + *

    + * When the context-propagation library + * is available at runtime and the downstream {@link ContextView} is not empty, this operator implicitly uses the + * library to restore thread locals around the handler {@link BiConsumer}. Typically, this would be done in conjunction + * with the use of {@link #contextCapture()} operator down the chain. * * @param handler the handling {@link BiConsumer} - * * @param the transformed type - * * @return a transformed {@link Flux} */ public final Flux handle(BiConsumer> handler) { @@ -5983,7 +6224,6 @@ @SuppressWarnings("unchecked") Callable thiz = (Callable)this; if(thiz instanceof Fuseable.ScalarCallable){ - @SuppressWarnings("unchecked") Fuseable.ScalarCallable c = (Fuseable.ScalarCallable)thiz; T v; try { @@ -6014,7 +6254,7 @@ * Typically used for scenarios where consumer(s) request a large amount of data * (eg. {@code Long.MAX_VALUE}) but the data source behaves better or can be optimized * with smaller requests (eg. database paging, etc...). All data is still processed, - * unlike with {@link #limitRequest(long)} which will cap the grand total request + * unlike with {@link #take(long)} which will cap the grand total request * amount. *

    * Equivalent to {@code flux.publishOn(Schedulers.immediate(), prefetchRate).subscribe() }. @@ -6028,7 +6268,7 @@ * * @return a {@link Flux} limiting downstream's backpressure * @see #publishOn(Scheduler, int) - * @see #limitRequest(long) + * @see #take(long) */ public final Flux limitRate(int prefetchRate) { return onAssembly(this.publishOn(Schedulers.immediate(), prefetchRate)); @@ -6046,7 +6286,7 @@ * Typically used for scenarios where consumer(s) request a large amount of data * (eg. {@code Long.MAX_VALUE}) but the data source behaves better or can be optimized * with smaller requests (eg. database paging, etc...). All data is still processed, - * unlike with {@link #limitRequest(long)} which will cap the grand total request + * unlike with {@link #take(long)} which will cap the grand total request * amount. *

    * Similar to {@code flux.publishOn(Schedulers.immediate(), prefetchRate).subscribe() }, @@ -6070,7 +6310,7 @@ * @return a {@link Flux} limiting downstream's backpressure and customizing the * replenishment request amount * @see #publishOn(Scheduler, int) - * @see #limitRequest(long) + * @see #take(long) */ public final Flux limitRate(int highTide, int lowTide) { return onAssembly(this.publishOn(Schedulers.immediate(), true, highTide, lowTide)); @@ -6430,15 +6670,18 @@ * The name serves as a prefix in the reported metrics names. In case no name has been provided, the default name "reactor" will be applied. *

    * The {@link MeterRegistry} used by reactor can be configured via - * {@link reactor.util.Metrics.MicrometerConfiguration#useRegistry(MeterRegistry)} + * {@link Metrics.MicrometerConfiguration#useRegistry(MeterRegistry)} * prior to using this operator, the default being * {@link io.micrometer.core.instrument.Metrics#globalRegistry}. * * @return an instrumented {@link Flux} * * @see #name(String) * @see #tag(String, String) + * @deprecated Prefer using the {@link #tap(SignalListenerFactory)} with the {@link SignalListenerFactory} provided by + * the new reactor-core-micrometer module. To be removed in 3.6.0 at the earliest. */ + @Deprecated public final Flux metrics() { if (!Metrics.isInstrumentationAvailable()) { return this; @@ -6454,7 +6697,8 @@ * Give a name to this sequence, which can be retrieved using {@link Scannable#name()} * as long as this is the first reachable {@link Scannable#parents()}. *

    - * If {@link #metrics()} operator is called later in the chain, this name will be used as a prefix for meters' name. + * The name is typically visible at assembly time by the {@link #tap(SignalListenerFactory)} operator, + * which could for example be configured with a metrics listener using the name as a prefix for meters' id. * * @param name a name for the sequence * @@ -6486,7 +6730,7 @@ /** * Evaluate each accepted value against the given {@link Class} type. If the - * a value matches the type, it is passed into the resulting {@link Flux}. Otherwise + * value matches the type, it is passed into the resulting {@link Flux}. Otherwise * the value is ignored and a request of 1 is emitted. * *

    @@ -6718,7 +6962,7 @@ *

    Discard Support: This operator discards elements that it drops after having passed * them to the provided {@code onDropped} handler. * - * @param onDropped the Consumer called when an value gets dropped due to lack of downstream requests + * @param onDropped the Consumer called when a value gets dropped due to lack of downstream requests * @return a backpressured {@link Flux} that drops overflowing elements */ public final Flux onBackpressureDrop(Consumer onDropped) { @@ -6759,6 +7003,52 @@ } /** + * Simply complete the sequence by replacing an {@link Subscriber#onError(Throwable) onError signal} + * with an {@link Subscriber#onComplete() onComplete signal}. All other signals are propagated as-is. + * + *

    + * + * + * @return a new {@link Flux} falling back on completion when an onError occurs + * @see #onErrorReturn(Object) + */ + public final Flux onErrorComplete() { + return onAssembly(new FluxOnErrorReturn<>(this, null, null)); + } + + /** + * Simply complete the sequence by replacing an {@link Subscriber#onError(Throwable) onError signal} + * with an {@link Subscriber#onComplete() onComplete signal} if the error matches the given + * {@link Class}. All other signals, including non-matching onError, are propagated as-is. + * + *

    + * + * + * @return a new {@link Flux} falling back on completion when a matching error occurs + * @see #onErrorReturn(Class, Object) + */ + public final Flux onErrorComplete(Class type) { + Objects.requireNonNull(type, "type must not be null"); + return onErrorComplete(type::isInstance); + } + + /** + * Simply complete the sequence by replacing an {@link Subscriber#onError(Throwable) onError signal} + * with an {@link Subscriber#onComplete() onComplete signal} if the error matches the given + * {@link Predicate}. All other signals, including non-matching onError, are propagated as-is. + * + *

    + * + * + * @return a new {@link Flux} falling back on completion when a matching error occurs + * @see #onErrorReturn(Predicate, Object) + */ + public final Flux onErrorComplete(Predicate predicate) { + Objects.requireNonNull(predicate, "predicate must not be null"); + return onAssembly(new FluxOnErrorReturn<>(this, predicate, null)); + } + + /** * Let compatible operators upstream recover from errors by dropping the * incriminating element from the sequence and continuing with subsequent elements. * The recovered error and associated value are notified via the provided {@link BiConsumer}. @@ -6791,7 +7081,7 @@ */ public final Flux onErrorContinue(BiConsumer errorConsumer) { BiConsumer genericConsumer = errorConsumer; - return subscriberContext(Context.of( + return contextWrite(Context.of( OnNextFailureStrategy.KEY_ON_NEXT_ERROR_STRATEGY, OnNextFailureStrategy.resume(genericConsumer) )); @@ -6875,7 +7165,7 @@ @SuppressWarnings("unchecked") Predicate genericPredicate = (Predicate) errorPredicate; BiConsumer genericErrorConsumer = errorConsumer; - return subscriberContext(Context.of( + return contextWrite(Context.of( OnNextFailureStrategy.KEY_ON_NEXT_ERROR_STRATEGY, OnNextFailureStrategy.resumeIf(genericPredicate, genericErrorConsumer) )); @@ -6892,7 +7182,7 @@ * was used downstream */ public final Flux onErrorStop() { - return subscriberContext(Context.of( + return contextWrite(Context.of( OnNextFailureStrategy.KEY_ON_NEXT_ERROR_STRATEGY, OnNextFailureStrategy.stop())); } @@ -7007,9 +7297,11 @@ * @param fallbackValue the value to emit if an error occurs * * @return a new falling back {@link Flux} + * @see #onErrorComplete() */ public final Flux onErrorReturn(T fallbackValue) { - return onErrorResume(t -> just(fallbackValue)); + Objects.requireNonNull(fallbackValue, "fallbackValue must not be null"); + return onAssembly(new FluxOnErrorReturn<>(this, null, fallbackValue)); } /** @@ -7023,10 +7315,11 @@ * @param the error type * * @return a new falling back {@link Flux} + * @see #onErrorComplete(Class) */ - public final Flux onErrorReturn(Class type, - T fallbackValue) { - return onErrorResume(type, t -> just(fallbackValue)); + public final Flux onErrorReturn(Class type, T fallbackValue) { + Objects.requireNonNull(type, "type must not be null"); + return onErrorReturn(type::isInstance, fallbackValue); } /** @@ -7039,9 +7332,12 @@ * @param fallbackValue the value to emit if an error occurs that matches the predicate * * @return a new falling back {@link Flux} + * @see #onErrorComplete(Predicate) */ public final Flux onErrorReturn(Predicate predicate, T fallbackValue) { - return onErrorResume(predicate, t -> just(fallbackValue)); + Objects.requireNonNull(predicate, "predicate must not be null"); + Objects.requireNonNull(fallbackValue, "fallbackValue must not be null"); + return onAssembly(new FluxOnErrorReturn<>(this, predicate, fallbackValue)); } /** @@ -7170,7 +7466,7 @@ */ public final ConnectableFlux publish(int prefetch) { return onAssembly(new FluxPublish<>(this, prefetch, Queues - .get(prefetch))); + .get(prefetch), true)); } /** @@ -7516,10 +7812,12 @@ * Will retain up to the given history size onNext signals. Completion and Error will also be * replayed. *

    - * Note that {@code cache(0)} will only cache the terminal signal without + * Note that {@code replay(0)} will only cache the terminal signal without * expiration. * *

    + * Re-connects are not supported. + *

    * * * @param history number of events retained in history excluding complete and @@ -7530,8 +7828,8 @@ */ public final ConnectableFlux replay(int history) { if (history == 0) { - //TODO Flux.replay with history == 0 doesn't make much sense. This was replaced by Flux.publish, but such calls will be rejected in a future version - return onAssembly(new FluxPublish<>(this, Queues.SMALL_BUFFER_SIZE, Queues.get(Queues.SMALL_BUFFER_SIZE))); + return onAssembly(new FluxPublish<>(this, Queues.SMALL_BUFFER_SIZE, + Queues.get(Queues.SMALL_BUFFER_SIZE), false)); } return onAssembly(new FluxReplay<>(this, history, 0L, null)); } @@ -7613,8 +7911,8 @@ public final ConnectableFlux replay(int history, Duration ttl, Scheduler timer) { Objects.requireNonNull(timer, "timer"); if (history == 0) { - //TODO Flux.replay with history == 0 doesn't make much sense. This was replaced by Flux.publish, but such calls will be rejected in a future version - return onAssembly(new FluxPublish<>(this, Queues.SMALL_BUFFER_SIZE, Queues.get(Queues.SMALL_BUFFER_SIZE))); + return onAssembly(new FluxPublish<>(this, Queues.SMALL_BUFFER_SIZE, + Queues.get(Queues.SMALL_BUFFER_SIZE), true)); } return onAssembly(new FluxReplay<>(this, history, ttl.toNanos(), timer)); } @@ -7652,7 +7950,7 @@ * The companion is generated by the provided {@link Retry} instance, see {@link Retry#max(long)}, {@link Retry#maxInARow(long)} * and {@link Retry#backoff(long, Duration)} for readily available strategy builders. *

    - * The operator generates a base for the companion, a {@link Flux} of {@link reactor.util.retry.Retry.RetrySignal} + * The operator generates a base for the companion, a {@link Flux} of {@link Retry.RetrySignal} * which each give metadata about each retryable failure whenever this {@link Flux} signals an error. The final companion * should be derived from that base companion and emit data in response to incoming onNext (although it can emit less * elements, or delay the emissions). @@ -7662,12 +7960,12 @@ *

    * *

    - * Note that the {@link reactor.util.retry.Retry.RetrySignal} state can be + * Note that the {@link Retry.RetrySignal} state can be * transient and change between each source * {@link org.reactivestreams.Subscriber#onError(Throwable) onError} or * {@link org.reactivestreams.Subscriber#onNext(Object) onNext}. If processed with a delay, * this could lead to the represented state being out of sync with the state at which the retry - * was evaluated. Map it to {@link reactor.util.retry.Retry.RetrySignal#copy()} + * was evaluated. Map it to {@link Retry.RetrySignal#copy()} * right away to mediate this. *

    * Note that if the companion {@link Publisher} created by the {@code whenFactory} @@ -7676,7 +7974,7 @@ *

     	 * {@code
     	 * Retry customStrategy = Retry.from(companion -> companion.handle((retrySignal, sink) -> {
    -	 * 	    Context ctx = sink.currentContext();
    +	 * 	    ContextView ctx = sink.contextView();
     	 * 	    int rl = ctx.getOrDefault("retriesLeft", 0);
     	 * 	    if (rl > 0) {
     	 *		    sink.next(Context.of(
    @@ -7692,12 +7990,12 @@
     	 * 
    * * @param retrySpec the {@link Retry} strategy that will generate the companion {@link Publisher}, - * given a {@link Flux} that signals each onError as a {@link reactor.util.retry.Retry.RetrySignal}. + * given a {@link Flux} that signals each onError as a {@link Retry.RetrySignal}. * * @return a {@link Flux} that retries on onError when a companion {@link Publisher} produces an onNext signal - * @see reactor.util.retry.Retry#max(long) - * @see reactor.util.retry.Retry#maxInARow(long) - * @see reactor.util.retry.Retry#backoff(long, Duration) + * @see Retry#max(long) + * @see Retry#maxInARow(long) + * @see Retry#backoff(long, Duration) */ public final Flux retryWhen(Retry retrySpec) { return onAssembly(new FluxRetryWhen<>(this, retrySpec)); @@ -7937,8 +8235,10 @@ * to subscribe once, late subscribers might therefore miss items. */ public final Flux share() { - return onAssembly(new FluxRefCount<>( - new FluxPublish<>(this, Queues.SMALL_BUFFER_SIZE, Queues.small()), 1) + return onAssembly( + new FluxRefCount<>(new FluxPublish<>( + this, Queues.SMALL_BUFFER_SIZE, Queues.small(), true + ), 1) ); } @@ -7959,7 +8259,7 @@ /** * Expect and emit a single item from this {@link Flux} source or signal - * {@link java.util.NoSuchElementException} for an empty source, or + * {@link NoSuchElementException} for an empty source, or * {@link IndexOutOfBoundsException} for a source with more than one element. * *

    @@ -8285,7 +8585,7 @@ * Subscribe a {@link Consumer} to this {@link Flux} that will consume all the * elements in the sequence. It will request an unbounded demand ({@code Long.MAX_VALUE}). *

    - * For a passive version that observe and forward incoming data see {@link #doOnNext(java.util.function.Consumer)}. + * For a passive version that observe and forward incoming data see {@link #doOnNext(Consumer)}. *

    * For a version that gives you more control over backpressure and the request, see * {@link #subscribe(Subscriber)} with a {@link BaseSubscriber}. @@ -8312,7 +8612,7 @@ * The subscription will request an unbounded demand ({@code Long.MAX_VALUE}). *

    * For a passive version that observe and forward incoming data see - * {@link #doOnNext(java.util.function.Consumer)} and {@link #doOnError(java.util.function.Consumer)}. + * {@link #doOnNext(Consumer)} and {@link #doOnError(Consumer)}. *

    For a version that gives you more control over backpressure and the request, see * {@link #subscribe(Subscriber)} with a {@link BaseSubscriber}. *

    @@ -8338,8 +8638,8 @@ * elements in the sequence, handle errors and react to completion. The subscription * will request unbounded demand ({@code Long.MAX_VALUE}). *

    - * For a passive version that observe and forward incoming data see {@link #doOnNext(java.util.function.Consumer)}, - * {@link #doOnError(java.util.function.Consumer)} and {@link #doOnComplete(Runnable)}. + * For a passive version that observe and forward incoming data see {@link #doOnNext(Consumer)}, + * {@link #doOnError(Consumer)} and {@link #doOnComplete(Runnable)}. *

    For a version that gives you more control over backpressure and the request, see * {@link #subscribe(Subscriber)} with a {@link BaseSubscriber}. *

    @@ -8370,8 +8670,8 @@ * request the adequate amount of data, or request unbounded demand * {@code Long.MAX_VALUE} if no such consumer is provided. *

    - * For a passive version that observe and forward incoming data see {@link #doOnNext(java.util.function.Consumer)}, - * {@link #doOnError(java.util.function.Consumer)}, {@link #doOnComplete(Runnable)} + * For a passive version that observe and forward incoming data see {@link #doOnNext(Consumer)}, + * {@link #doOnError(Consumer)}, {@link #doOnComplete(Runnable)} * and {@link #doOnSubscribe(Consumer)}. *

    For a version that gives you more control over backpressure and the request, see * {@link #subscribe(Subscriber)} with a {@link BaseSubscriber}. @@ -8410,8 +8710,8 @@ * elements in the sequence, handle errors and react to completion. Additionally, a {@link Context} * is tied to the subscription. At subscription, an unbounded request is implicitly made. *

    - * For a passive version that observe and forward incoming data see {@link #doOnNext(java.util.function.Consumer)}, - * {@link #doOnError(java.util.function.Consumer)}, {@link #doOnComplete(Runnable)} + * For a passive version that observe and forward incoming data see {@link #doOnNext(Consumer)}, + * {@link #doOnError(Consumer)}, {@link #doOnComplete(Runnable)} * and {@link #doOnSubscribe(Consumer)}. *

    For a version that gives you more control over backpressure and the request, see * {@link #subscribe(Subscriber)} with a {@link BaseSubscriber}. @@ -8448,6 +8748,10 @@ CorePublisher publisher = Operators.onLastAssembly(this); CoreSubscriber subscriber = Operators.toCoreSubscriber(actual); + if (subscriber instanceof Fuseable.QueueSubscription && this != publisher && this instanceof Fuseable && !(publisher instanceof Fuseable)) { + subscriber = new FluxHide.SuppressFuseableSubscriber<>(subscriber); + } + try { if (publisher instanceof OptimizableOperator) { OptimizableOperator operator = (OptimizableOperator) publisher; @@ -8487,62 +8791,13 @@ public abstract void subscribe(CoreSubscriber actual); /** - * Enrich a potentially empty downstream {@link Context} by adding all values - * from the given {@link Context}, producing a new {@link Context} that is propagated - * upstream. - *

    - * The {@link Context} propagation happens once per subscription (not on each onNext): - * it is done during the {@code subscribe(Subscriber)} phase, which runs from - * the last operator of a chain towards the first. - *

    - * So this operator enriches a {@link Context} coming from under it in the chain - * (downstream, by default an empty one) and makes the new enriched {@link Context} - * visible to operators above it in the chain. - * - * @param mergeContext the {@link Context} to merge with a previous {@link Context} - * state, returning a new one. - * - * @return a contextualized {@link Flux} - * @see Context - * @deprecated Use {@link #contextWrite(ContextView)} instead. To be removed in 3.5.0. - */ - @Deprecated - public final Flux subscriberContext(Context mergeContext) { - return subscriberContext(c -> c.putAll(mergeContext.readOnly())); - } - - /** - * Enrich a potentially empty downstream {@link Context} by applying a {@link Function} - * to it, producing a new {@link Context} that is propagated upstream. - *

    - * The {@link Context} propagation happens once per subscription (not on each onNext): - * it is done during the {@code subscribe(Subscriber)} phase, which runs from - * the last operator of a chain towards the first. - *

    - * So this operator enriches a {@link Context} coming from under it in the chain - * (downstream, by default an empty one) and makes the new enriched {@link Context} - * visible to operators above it in the chain. - * - * @param doOnContext the function taking a previous {@link Context} state - * and returning a new one. - * - * @return a contextualized {@link Flux} - * @see Context - * @deprecated Use {@link #contextWrite(Function)} instead. To be removed in 3.5.0. - */ - @Deprecated - public final Flux subscriberContext(Function doOnContext) { - return contextWrite(doOnContext); - } - - /** * Run subscribe, onSubscribe and request on a specified {@link Scheduler}'s {@link Worker}. * As such, placing this operator anywhere in the chain will also impact the execution * context of onNext/onError/onComplete signals from the beginning of the chain up to * the next occurrence of a {@link #publishOn(Scheduler) publishOn}. *

    * Note that if you are using an eager or blocking - * {@link #create(Consumer, FluxSink.OverflowStrategy)} + * {@link #create(Consumer, OverflowStrategy)} * as the source, it can lead to deadlocks due to requests piling up behind the emitter. * In such case, you should call {@link #subscribeOn(Scheduler, boolean) subscribeOn(scheduler, false)} * instead. @@ -8579,7 +8834,7 @@ * the next occurrence of a {@link #publishOn(Scheduler) publishOn}. *

    * Note that if you are using an eager or blocking - * {@link Flux#create(Consumer, FluxSink.OverflowStrategy)} + * {@link Flux#create(Consumer, OverflowStrategy)} * as the source, it can lead to deadlocks due to requests piling up behind the emitter. * Thus this operator has a {@code requestOnSeparateThread} parameter, which should be * set to {@code false} in this case. @@ -8624,13 +8879,10 @@ } /** - * Subscribe the given {@link Subscriber} to this {@link Flux} and return said - * {@link Subscriber} (eg. a {@link FluxProcessor}). - * - *

    -	 * {@code flux.subscribeWith(EmitterProcessor.create()).subscribe() }
    -	 * 
    - * + * Subscribe a provided instance of a subclass of {@link Subscriber} to this {@link Flux} + * and return said instance for further chaining calls. This is similar to {@link #as(Function)}, + * except a subscription is explicitly performed by this method. + *

    * If you need more control over backpressure and the request, use a {@link BaseSubscriber}. * * @param subscriber the {@link Subscriber} to subscribe with and return @@ -8760,16 +9012,19 @@ * *

    * + *

    + * This operator requests the source for an unbounded amount, but doesn't + * request each generated {@link Publisher} unless the downstream has made + * a corresponding request (no prefetch of inner publishers). * * @param fn the {@link Function} to generate a {@link Publisher} for each source value * @param the type of the return value of the transformation function * * @return a new {@link Flux} that emits values from an alternative {@link Publisher} * for each source onNext - * */ public final Flux switchMap(Function> fn) { - return switchMap(fn, Queues.XS_BUFFER_SIZE); + return onAssembly(new FluxSwitchMapNoPrefetch<>(this, fn)); } /** @@ -8803,11 +9058,10 @@ /** * Tag this flux with a key/value pair. These can be retrieved as a {@link Set} of * all tags throughout the publisher chain by using {@link Scannable#tags()} (as - * traversed - * by {@link Scannable#parents()}). + * traversed by {@link Scannable#parents()}). *

    - * Note that some monitoring systems like Prometheus require to have the exact same set of - * tags for each meter bearing the same name. + * The name is typically visible at assembly time by the {@link #tap(SignalListenerFactory)} operator, + * which could for example be configured with a metrics listener applying the tag(s) to its meters. * * @param key a tag key * @param value a tag value @@ -8823,27 +9077,28 @@ /** * Take only the first N values from this {@link Flux}, if available. - * If n is zero, the source is subscribed to but immediately cancelled, then the operator completes. + * If n is zero, the source isn't even subscribed to and the operator completes immediately upon subscription. *

    - * + * *

    - * Warning: The below behavior will change in 3.5.0 from that of - * {@link #take(long, boolean) take(n, false)} to that of {@link #take(long, boolean) take(n, true)}. - * See https://github.com/reactor/reactor-core/issues/2339 + * This ensures that the total amount requested upstream is capped at {@code n}, although smaller + * requests can be made if the downstream makes requests < n. In any case, this operator never lets + * the upstream produce more elements than the cap, and it can be used to more strictly adhere to backpressure. *

    - * Note that this operator doesn't propagate the backpressure requested amount. - * Rather, it makes an unbounded request and cancels once N elements have been emitted. - * As a result, the source could produce a lot of extraneous elements in the meantime. - * If that behavior is undesirable and you do not own the request from downstream - * (e.g. prefetching operators), consider using {@link #limitRequest(long)} instead. - * - * @param n the number of items to emit from this {@link Flux} + * This mode is typically useful for cases where a race between request and cancellation can lead + * the upstream to producing a lot of extraneous data, and such a production is undesirable (e.g. + * a source that would send the extraneous data over the network). + * It is equivalent to {@link #take(long, boolean)} with {@code limitRequest == true}, + * If there is a requirement for unbounded upstream request (eg. for performance reasons), + * use {@link #take(long, boolean)} with {@code limitRequest=false} instead. * + * @param n the maximum number of items to request from upstream and emit from this {@link Flux} + * * @return a {@link Flux} limited to size N * @see #take(long, boolean) */ public final Flux take(long n) { - return take(n, false); + return take(n, true); } /** @@ -8855,7 +9110,7 @@ * at {@code n}. In that configuration, this operator never let the upstream produce more elements * than the cap, and it can be used to more strictly adhere to backpressure. * If n is zero, the source isn't even subscribed to and the operator completes immediately - * upon subscription. + * upon subscription (the behavior inherited from {@link #take(long)}). *

    * This mode is typically useful for cases where a race between request and cancellation can lead * the upstream to producing a lot of extraneous data, and such a production is undesirable (e.g. @@ -8865,8 +9120,7 @@ *

    * If {@code limitRequest == false} this operator doesn't propagate the backpressure requested amount. * Rather, it makes an unbounded request and cancels once N elements have been emitted. - * If n is zero, the source is subscribed to but immediately cancelled, then the operator completes - * (the behavior inherited from {@link #take(long)}). + * If n is zero, the source is subscribed to but immediately cancelled, then the operator completes. *

    * In this mode, the source could produce a lot of extraneous elements despite cancellation. * If that behavior is undesirable and you do not own the request from downstream @@ -8892,7 +9146,7 @@ * Relay values from this {@link Flux} until the specified {@link Duration} elapses. *

    * If the duration is zero, the resulting {@link Flux} completes as soon as this {@link Flux} - * signals its first value (which is not not relayed, though). + * signals its first value (which is not relayed, though). * *

    * @@ -8911,7 +9165,7 @@ * as measured on the specified {@link Scheduler}. *

    * If the duration is zero, the resulting {@link Flux} completes as soon as this {@link Flux} - * signals its first value (which is not not relayed, though). + * signals its first value (which is not relayed, though). * *

    * @@ -8927,7 +9181,7 @@ return takeUntilOther(Mono.delay(timespan, timer)); } else { - return take(0); + return take(0, false); } } @@ -8952,6 +9206,9 @@ /** * Relay values from this {@link Flux} until the given {@link Predicate} matches. * This includes the matching data (unlike {@link #takeWhile}). + * The predicate is tested before the element is emitted, + * so if the element is modified by the consumer, this won't affect the predicate. + * In case of an error during the predicate test, the current element is emitted before the error. * *

    * @@ -8999,6 +9256,114 @@ } /** + * Tap into Reactive Streams signals emitted or received by this {@link Flux} and notify a stateful per-{@link Subscriber} + * {@link SignalListener}. + *

    + * Any exception thrown by the {@link SignalListener} methods causes the subscription to be cancelled + * and the subscriber to be terminated with an {@link Subscriber#onError(Throwable) onError signal} of that + * exception. Note that {@link SignalListener#doFinally(SignalType)}, {@link SignalListener#doAfterComplete()} and + * {@link SignalListener#doAfterError(Throwable)} instead just {@link Operators#onErrorDropped(Throwable, Context) drop} + * the exception. + *

    + * This simplified variant assumes the state is purely initialized within the {@link Supplier}, + * as it is called for each incoming {@link Subscriber} without additional context. + *

    + * When the context-propagation library + * is available at runtime and the downstream {@link ContextView} is not empty, this operator implicitly uses the library + * to restore thread locals around all invocations of {@link SignalListener} methods. Typically, this would be done + * in conjunction with the use of {@link #contextCapture()} operator down the chain. + * + * @param simpleListenerGenerator the {@link Supplier} to create a new {@link SignalListener} on each subscription + * @return a new {@link Flux} with side effects defined by generated {@link SignalListener} + * @see #tap(Function) + * @see #tap(SignalListenerFactory) + */ + public final Flux tap(Supplier> simpleListenerGenerator) { + return tap(new SignalListenerFactory() { + @Override + public Void initializePublisherState(Publisher ignored) { + return null; + } + + @Override + public SignalListener createListener(Publisher ignored1, ContextView ignored2, Void ignored3) { + return simpleListenerGenerator.get(); + } + }); + } + + /** + * Tap into Reactive Streams signals emitted or received by this {@link Flux} and notify a stateful per-{@link Subscriber} + * {@link SignalListener}. + *

    + * Any exception thrown by the {@link SignalListener} methods causes the subscription to be cancelled + * and the subscriber to be terminated with an {@link Subscriber#onError(Throwable) onError signal} of that + * exception. Note that {@link SignalListener#doFinally(SignalType)}, {@link SignalListener#doAfterComplete()} and + * {@link SignalListener#doAfterError(Throwable)} instead just {@link Operators#onErrorDropped(Throwable, Context) drop} + * the exception. + *

    + * This simplified variant allows the {@link SignalListener} to be constructed for each subscription + * with access to the incoming {@link Subscriber}'s {@link ContextView}. + *

    + * When the context-propagation library + * is available at runtime and the {@link ContextView} is not empty, this operator implicitly uses the library + * to restore thread locals around all invocations of {@link SignalListener} methods. Typically, this would be done + * in conjunction with the use of {@link #contextCapture()} operator down the chain. + * + * @param listenerGenerator the {@link Function} to create a new {@link SignalListener} on each subscription + * @return a new {@link Flux} with side effects defined by generated {@link SignalListener} + * @see #tap(Supplier) + * @see #tap(SignalListenerFactory) + */ + public final Flux tap(Function> listenerGenerator) { + return tap(new SignalListenerFactory() { + @Override + public Void initializePublisherState(Publisher ignored) { + return null; + } + + @Override + public SignalListener createListener(Publisher ignored1, ContextView listenerContext, Void ignored2) { + return listenerGenerator.apply(listenerContext); + } + }); + } + + /** + * Tap into Reactive Streams signals emitted or received by this {@link Flux} and notify a stateful per-{@link Subscriber} + * {@link SignalListener} created by the provided {@link SignalListenerFactory}. + *

    + * The factory will initialize a {@link SignalListenerFactory#initializePublisherState(Publisher) state object} for + * each {@link Flux} or {@link Mono} instance it is used with, and that state will be cached and exposed for each + * incoming {@link Subscriber} in order to generate the associated {@link SignalListenerFactory#createListener(Publisher, ContextView, Object) listener}. + *

    + * Any exception thrown by the {@link SignalListener} methods causes the subscription to be cancelled + * and the subscriber to be terminated with an {@link Subscriber#onError(Throwable) onError signal} of that + * exception. Note that {@link SignalListener#doFinally(SignalType)}, {@link SignalListener#doAfterComplete()} and + * {@link SignalListener#doAfterError(Throwable)} instead just {@link Operators#onErrorDropped(Throwable, Context) drop} + * the exception. + *

    + * When the context-propagation library + * is available at runtime and the downstream {@link ContextView} is not empty, this operator implicitly uses the library + * to restore thread locals around all invocations of {@link SignalListener} methods. Typically, this would be done + * in conjunction with the use of {@link #contextCapture()} operator down the chain. + * + * @param listenerFactory the {@link SignalListenerFactory} to create a new {@link SignalListener} on each subscription + * @return a new {@link Flux} with side effects defined by generated {@link SignalListener} + * @see #tap(Supplier) + * @see #tap(Function) + */ + public final Flux tap(SignalListenerFactory listenerFactory) { + if (ContextPropagationSupport.shouldPropagateContextToThreadLocals()) { + return onAssembly(new FluxTapRestoringThreadLocals<>(this, listenerFactory)); + } + if (this instanceof Fuseable) { + return onAssembly(new FluxTapFuseable<>(this, listenerFactory)); + } + return onAssembly(new FluxTap<>(this, listenerFactory)); + } + + /** * Return a {@code Mono} that completes when this {@link Flux} completes. * This will actively ignore the sequence and only replay completion or error signals. *

    @@ -9071,7 +9436,6 @@ */ public final Flux thenMany(Publisher other) { if (this instanceof FluxConcatArray) { - @SuppressWarnings({ "unchecked" }) FluxConcatArray fluxConcatArray = (FluxConcatArray) this; return fluxConcatArray.concatAdditionalIgnoredLast(other); } @@ -9261,7 +9625,7 @@ return timeout(firstTimeout, nextTimeoutFactory, "first signal from a Publisher"); } - private final Flux timeout(Publisher firstTimeout, + private Flux timeout(Publisher firstTimeout, Function> nextTimeoutFactory, String timeoutDescription) { return onAssembly(new FluxTimeout<>(this, firstTimeout, nextTimeoutFactory, timeoutDescription)); @@ -9294,7 +9658,7 @@ } /** - * Emit a {@link reactor.util.function.Tuple2} pair of T1 the current clock time in + * Emit a {@link Tuple2} pair of T1 the current clock time in * millis (as a {@link Long} measured by the {@link Schedulers#parallel() parallel} * Scheduler) and T2 the emitted data (as a {@code T}), for each item from this {@link Flux}. * @@ -9310,7 +9674,7 @@ } /** - * Emit a {@link reactor.util.function.Tuple2} pair of T1 the current clock time in + * Emit a {@link Tuple2} pair of T1 the current clock time in * millis (as a {@link Long} measured by the provided {@link Scheduler}) and T2 * the emitted data (as a {@code T}), for each item from this {@link Flux}. * @@ -9394,7 +9758,10 @@ else{ provider = () -> Hooks.wrapQueue(queueProvider.get()); } - return new BlockingIterable<>(this, batchSize, provider); + Supplier contextSupplier = + ContextPropagationSupport.shouldPropagateContextToThreadLocals() ? + ContextPropagation::contextCaptureToEmpty : Context::empty; + return new BlockingIterable<>(this, batchSize, provider, contextSupplier); } /** @@ -9432,7 +9799,10 @@ public final Stream toStream(int batchSize) { final Supplier> provider; provider = Queues.get(batchSize); - return new BlockingIterable<>(this, batchSize, provider).stream(); + Supplier contextSupplier = + ContextPropagationSupport.shouldPropagateContextToThreadLocals() ? + ContextPropagation::contextCaptureToEmpty : Context::empty; + return new BlockingIterable<>(this, batchSize, provider, contextSupplier).stream(); } /** @@ -9543,6 +9913,11 @@ * This is most noticeable when trying to {@link #retry()} or {@link #repeat()} a window, * as these operators are based on re-subscription. * + *

    + * To distinguish errors emitted by the processing of individual windows, source + * sequence errors delivered to the window {@link Flux} are wrapped in + * {@link Exceptions.SourceException}. + * *

    Discard Support: This operator discards elements it internally queued for backpressure * upon cancellation or error triggered by a data signal. * @@ -9578,6 +9953,11 @@ * This is most noticeable when trying to {@link #retry()} or {@link #repeat()} a window, * as these operators are based on re-subscription. * + *

    + * To distinguish errors emitted by the processing of individual windows, source + * sequence errors delivered to the window {@link Flux} are wrapped in + * {@link Exceptions.SourceException}. + * *

    Discard Support: The overlapping variant DOES NOT discard elements, as they might be part of another still valid window. * The exact window and dropping window variants bot discard elements they internally queued for backpressure * upon cancellation or error triggered by a data signal. The dropping window variant also discards elements in between windows. @@ -9609,6 +9989,11 @@ * This is most noticeable when trying to {@link #retry()} or {@link #repeat()} a window, * as these operators are based on re-subscription. * + *

    + * To distinguish errors emitted by the processing of individual windows, source + * sequence errors and those emitted by the {@code boundary} delivered to the window + * {@link Flux} are wrapped in {@link Exceptions.SourceException}. + * *

    Discard Support: This operator discards elements it internally queued for backpressure * upon cancellation or error triggered by a data signal. * @@ -9636,6 +10021,11 @@ * This is most noticeable when trying to {@link #retry()} or {@link #repeat()} a window, * as these operators are based on re-subscription. * + *

    + * To distinguish errors emitted by the processing of individual windows, source + * sequence errors delivered to the window {@link Flux} are wrapped in + * {@link Exceptions.SourceException}. + * *

    Discard Support: This operator discards elements it internally queued for backpressure * upon cancellation or error triggered by a data signal. * @@ -9674,6 +10064,11 @@ * This is most noticeable when trying to {@link #retry()} or {@link #repeat()} a window, * as these operators are based on re-subscription. * + *

    + * To distinguish errors emitted by the processing of individual windows, source + * sequence errors delivered to the window {@link Flux} are wrapped in + * {@link Exceptions.SourceException}. + * *

    Discard Support: The overlapping variant DOES NOT discard elements, as they might be part of another still valid window. * The exact window and dropping window variants bot discard elements they internally queued for backpressure * upon cancellation or error triggered by a data signal. The dropping window variant also discards elements in between windows. @@ -9703,6 +10098,11 @@ * This is most noticeable when trying to {@link #retry()} or {@link #repeat()} a window, * as these operators are based on re-subscription. * + *

    + * To distinguish errors emitted by the processing of individual windows, source + * sequence errors delivered to the window {@link Flux} are wrapped in + * {@link Exceptions.SourceException}. + * *

    Discard Support: This operator discards elements it internally queued for backpressure * upon cancellation or error triggered by a data signal. * @@ -9742,6 +10142,11 @@ * This is most noticeable when trying to {@link #retry()} or {@link #repeat()} a window, * as these operators are based on re-subscription. * + *

    + * To distinguish errors emitted by the processing of individual windows, source + * sequence errors delivered to the window {@link Flux} are wrapped in + * {@link Exceptions.SourceException}. + * *

    Discard Support: The overlapping variant DOES NOT discard elements, as they might be part of another still valid window. * The exact window and dropping window variants bot discard elements they internally queued for backpressure * upon cancellation or error triggered by a data signal. The dropping window variant also discards elements in between windows. @@ -9777,6 +10182,11 @@ * This is most noticeable when trying to {@link #retry()} or {@link #repeat()} a window, * as these operators are based on re-subscription. * + *

    + * To distinguish errors emitted by the processing of individual windows, source + * sequence errors delivered to the window {@link Flux} are wrapped in + * {@link Exceptions.SourceException}. + * *

    Discard Support: This operator discards elements it internally queued for backpressure * upon cancellation or error triggered by a data signal. * @@ -9793,6 +10203,42 @@ * Split this {@link Flux} sequence into multiple {@link Flux} windows containing * {@code maxSize} elements (or less for the final window) and starting from the first item. * Each {@link Flux} window will onComplete once it contains {@code maxSize} elements + * OR it has been open for the given {@link Duration} (as measured on the {@link Schedulers#parallel() parallel} + * Scheduler). + * + *

    + * + * + *

    + * Note that windows are a live view of part of the underlying source publisher, + * and as such their lifecycle is tied to that source. As a result, it is not possible + * to subscribe to a window more than once: they are unicast. + * This is most noticeable when trying to {@link #retry()} or {@link #repeat()} a window, + * as these operators are based on re-subscription. + * + *

    + * To distinguish errors emitted by the processing of individual windows, source + * sequence errors delivered to the window {@link Flux} are wrapped in + * {@link Exceptions.SourceException}. + * + *

    Discard Support: This operator discards elements it internally queued for backpressure + * upon cancellation or error triggered by a data signal. + * + * @param maxSize the maximum number of items to emit in the window before closing it + * @param maxTime the maximum {@link Duration} since the window was opened before closing it + * @param fairBackpressure define whether operator request unbounded demand or + * prefetch by maxSize + * + * @return a {@link Flux} of {@link Flux} windows based on element count and duration + */ + public final Flux> windowTimeout(int maxSize, Duration maxTime, boolean fairBackpressure) { + return windowTimeout(maxSize, maxTime , Schedulers.parallel(), fairBackpressure); + } + + /** + * Split this {@link Flux} sequence into multiple {@link Flux} windows containing + * {@code maxSize} elements (or less for the final window) and starting from the first item. + * Each {@link Flux} window will onComplete once it contains {@code maxSize} elements * OR it has been open for the given {@link Duration} (as measured on the provided * {@link Scheduler}). * @@ -9806,6 +10252,11 @@ * This is most noticeable when trying to {@link #retry()} or {@link #repeat()} a window, * as these operators are based on re-subscription. * + *

    + * To distinguish errors emitted by the processing of individual windows, source + * sequence errors delivered to the window {@link Flux} are wrapped in + * {@link Exceptions.SourceException}. + * *

    Discard Support: This operator discards elements it internally queued for backpressure * upon cancellation or error triggered by a data signal. * @@ -9816,10 +10267,47 @@ * @return a {@link Flux} of {@link Flux} windows based on element count and duration */ public final Flux> windowTimeout(int maxSize, Duration maxTime, Scheduler timer) { - return onAssembly(new FluxWindowTimeout<>(this, maxSize, maxTime.toNanos(), TimeUnit.NANOSECONDS, timer)); + return windowTimeout(maxSize, maxTime, timer, false); } /** + * Split this {@link Flux} sequence into multiple {@link Flux} windows containing + * {@code maxSize} elements (or less for the final window) and starting from the first item. + * Each {@link Flux} window will onComplete once it contains {@code maxSize} elements + * OR it has been open for the given {@link Duration} (as measured on the provided + * {@link Scheduler}). + * + *

    + * + * + *

    + * Note that windows are a live view of part of the underlying source publisher, + * and as such their lifecycle is tied to that source. As a result, it is not possible + * to subscribe to a window more than once: they are unicast. + * This is most noticeable when trying to {@link #retry()} or {@link #repeat()} a window, + * as these operators are based on re-subscription. + * + *

    + * To distinguish errors emitted by the processing of individual windows, source + * sequence errors delivered to the window {@link Flux} are wrapped in + * {@link Exceptions.SourceException}. + * + *

    Discard Support: This operator discards elements it internally queued for backpressure + * upon cancellation or error triggered by a data signal. + * + * @param maxSize the maximum number of items to emit in the window before closing it + * @param maxTime the maximum {@link Duration} since the window was opened before closing it + * @param timer a time-capable {@link Scheduler} instance to run on + * @param fairBackpressure define whether operator request unbounded demand or + * prefetch by maxSize + * + * @return a {@link Flux} of {@link Flux} windows based on element count and duration + */ + public final Flux> windowTimeout(int maxSize, Duration maxTime, Scheduler timer, boolean fairBackpressure) { + return onAssembly(new FluxWindowTimeout<>(this, maxSize, maxTime.toNanos(), TimeUnit.NANOSECONDS, timer, fairBackpressure)); + } + + /** * Split this {@link Flux} sequence into multiple {@link Flux} windows delimited by the * given predicate. A new window is opened each time the predicate returns true, at which * point the previous window will receive the triggering element then onComplete. @@ -9839,6 +10327,11 @@ * This is most noticeable when trying to {@link #retry()} or {@link #repeat()} a window, * as these operators are based on re-subscription. * + *

    + * To distinguish errors emitted by the processing of individual windows, source + * sequence errors delivered to the window {@link Flux} are wrapped in + * {@link Exceptions.SourceException}. + * *

    Discard Support: This operator discards elements it internally queued for backpressure * upon cancellation or error triggered by a data signal. Upon cancellation of the current window, * it also discards the remaining elements that were bound for it until the main sequence completes @@ -9876,6 +10369,11 @@ * This is most noticeable when trying to {@link #retry()} or {@link #repeat()} a window, * as these operators are based on re-subscription. * + *

    + * To distinguish errors emitted by the processing of individual windows, source + * sequence errors delivered to the window {@link Flux} are wrapped in + * {@link Exceptions.SourceException}. + * *

    Discard Support: This operator discards elements it internally queued for backpressure * upon cancellation or error triggered by a data signal. Upon cancellation of the current window, * it also discards the remaining elements that were bound for it until the main sequence completes @@ -9915,6 +10413,11 @@ * This is most noticeable when trying to {@link #retry()} or {@link #repeat()} a window, * as these operators are based on re-subscription. * + *

    + * To distinguish errors emitted by the processing of individual windows, source + * sequence errors delivered to the window {@link Flux} are wrapped in + * {@link Exceptions.SourceException}. + * *

    Discard Support: This operator discards elements it internally queued for backpressure * upon cancellation or error triggered by a data signal. Upon cancellation of the current window, * it also discards the remaining elements that were bound for it until the main sequence completes @@ -9949,14 +10452,19 @@ * This is most noticeable when trying to {@link #retry()} or {@link #repeat()} a window, * as these operators are based on re-subscription. * + *

    + * To distinguish errors emitted by the processing of individual windows, source + * sequence errors delivered to the window {@link Flux} are wrapped in + * {@link Exceptions.SourceException}. + * *

    Discard Support: This operator discards elements it internally queued for backpressure * upon cancellation or error triggered by a data signal. Upon cancellation of the current window, * it also discards the remaining elements that were bound for it until the main sequence completes * or creation of a new window is triggered. * * @return a microbatched {@link Flux} of {@link Flux} windows. */ - public final Flux> windowUntilChanged() { + public final Flux> windowUntilChanged() { return windowUntilChanged(identityFunction()); } @@ -9975,6 +10483,11 @@ * This is most noticeable when trying to {@link #retry()} or {@link #repeat()} a window, * as these operators are based on re-subscription. * + *

    + * To distinguish errors emitted by the processing of individual windows, source + * sequence errors delivered to the window {@link Flux} are wrapped in + * {@link Exceptions.SourceException}. + * *

    Discard Support: This operator discards elements it internally queued for backpressure * upon cancellation or error triggered by a data signal. Upon cancellation of the current window, * it also discards the remaining elements that were bound for it until the main sequence completes @@ -10003,6 +10516,11 @@ * This is most noticeable when trying to {@link #retry()} or {@link #repeat()} a window, * as these operators are based on re-subscription. * + *

    + * To distinguish errors emitted by the processing of individual windows, source + * sequence errors delivered to the window {@link Flux} are wrapped in + * {@link Exceptions.SourceException}. + * *

    Discard Support: This operator discards elements it internally queued for backpressure * upon cancellation or error triggered by a data signal. Upon cancellation of the current window, * it also discards the remaining elements that were bound for it until the main sequence completes @@ -10038,6 +10556,11 @@ * This is most noticeable when trying to {@link #retry()} or {@link #repeat()} a window, * as these operators are based on re-subscription. * + *

    + * To distinguish errors emitted by the processing of individual windows, source + * sequence errors delivered to the window {@link Flux} are wrapped in + * {@link Exceptions.SourceException}. + * *

    Discard Support: This operator discards elements it internally queued for backpressure * upon cancellation or error triggered by a data signal, as well as the triggering element(s) (that doesn't match * the predicate). Upon cancellation of the current window, it also discards the remaining elements @@ -10071,6 +10594,11 @@ * This is most noticeable when trying to {@link #retry()} or {@link #repeat()} a window, * as these operators are based on re-subscription. * + *

    + * To distinguish errors emitted by the processing of individual windows, source + * sequence errors delivered to the window {@link Flux} are wrapped in + * {@link Exceptions.SourceException}. + * *

    Discard Support: This operator discards elements it internally queued for backpressure * upon cancellation or error triggered by a data signal, as well as the triggering element(s) (that doesn't match * the predicate). Upon cancellation of the current window, it also discards the remaining elements @@ -10110,6 +10638,11 @@ * This is most noticeable when trying to {@link #retry()} or {@link #repeat()} a window, * as these operators are based on re-subscription. * + *

    + * To distinguish errors emitted by the processing of individual windows, source + * sequence errors delivered to the window {@link Flux} are wrapped in + * {@link Exceptions.SourceException}. + * *

    Discard Support: This operator DOES NOT discard elements. * * @param bucketOpening a {@link Publisher} that opens a new window when it emits any item @@ -10196,7 +10729,6 @@ public final Flux zipWith(Publisher source2, final BiFunction combinator) { if (this instanceof FluxZip) { - @SuppressWarnings("unchecked") FluxZip o = (FluxZip) this; Flux result = o.zipAdditionalSource(source2, combinator); if (result != null) { @@ -10268,7 +10800,6 @@ * @return a zipped {@link Flux} * */ - @SuppressWarnings("unchecked") public final Flux> zipWithIterable(Iterable iterable) { return zipWithIterable(iterable, tuple2Function()); } @@ -10370,7 +10901,6 @@ FluxConcatMap.ErrorMode.IMMEDIATE)); } - @SuppressWarnings("unchecked") static Flux doOnSignal(Flux source, @Nullable Consumer onSubscribe, @Nullable Consumer onNext, @@ -10573,8 +11103,9 @@ @SuppressWarnings("rawtypes") static final Supplier SET_SUPPLIER = HashSet::new; static final BooleanSupplier ALWAYS_BOOLEAN_SUPPLIER = () -> true; + @SuppressWarnings("rawtypes") static final BiPredicate OBJECT_EQUAL = Object::equals; @SuppressWarnings("rawtypes") static final Function IDENTITY_FUNCTION = Function.identity(); -} +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxBufferPredicate.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxBufferPredicate.java (.../FluxBufferPredicate.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxBufferPredicate.java (.../FluxBufferPredicate.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -40,7 +40,7 @@ /** * Buffers elements into custom collections where the buffer boundary is determined by - * a {@link java.util.function.Predicate} on the values. The predicate can be used in + * a {@link Predicate} on the values. The predicate can be used in * several modes: *

      *
    • {@code Until}: A new buffer starts when the predicate returns true. The @@ -461,4 +461,4 @@ } } -} +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxBufferTimeout.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxBufferTimeout.java (.../FluxBufferTimeout.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxBufferTimeout.java (.../FluxBufferTimeout.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ import java.util.Collection; import java.util.Objects; +import java.util.Queue; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; @@ -30,26 +31,30 @@ import reactor.core.Disposable; import reactor.core.Exceptions; import reactor.core.scheduler.Scheduler; +import reactor.util.Logger; import reactor.util.annotation.Nullable; +import reactor.util.concurrent.Queues; import reactor.util.context.Context; /** * @author Stephane Maldini */ final class FluxBufferTimeout> extends InternalFluxOperator { - final int batchSize; - final Supplier bufferSupplier; - final Scheduler timer; - final long timespan; - final TimeUnit unit; + final int batchSize; + final Supplier bufferSupplier; + final Scheduler timer; + final long timespan; + final TimeUnit unit; + final boolean fairBackpressure; FluxBufferTimeout(Flux source, int maxSize, long timespan, TimeUnit unit, Scheduler timer, - Supplier bufferSupplier) { + Supplier bufferSupplier, + boolean fairBackpressure) { super(source); if (timespan <= 0) { throw new IllegalArgumentException("Timeout period must be strictly positive"); @@ -62,10 +67,20 @@ this.unit = Objects.requireNonNull(unit, "unit"); this.batchSize = maxSize; this.bufferSupplier = Objects.requireNonNull(bufferSupplier, "bufferSupplier"); + this.fairBackpressure = fairBackpressure; } @Override public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + if (fairBackpressure) { + return new BufferTimeoutWithBackpressureSubscriber<>(actual, + batchSize, + timespan, + unit, + timer.createWorker(), + bufferSupplier, + null); + } return new BufferTimeoutSubscriber<>( Operators.serialize(actual), batchSize, @@ -84,6 +99,435 @@ return super.scanUnsafe(key); } + final static class BufferTimeoutWithBackpressureSubscriber> + implements InnerOperator { + + @Nullable + final Logger logger; + final CoreSubscriber actual; + final int batchSize; + final int prefetch; + final long timeSpan; + final TimeUnit unit; + final Scheduler.Worker timer; + final Supplier bufferSupplier; + + // tracks unsatisfied downstream demand (expressed in # of buffers) + volatile long requested; + @SuppressWarnings("rawtypes") + private AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(BufferTimeoutWithBackpressureSubscriber.class, "requested"); + + // tracks undelivered values in the current buffer + volatile int index; + @SuppressWarnings("rawtypes") + private AtomicIntegerFieldUpdater INDEX = + AtomicIntegerFieldUpdater.newUpdater(BufferTimeoutWithBackpressureSubscriber.class, "index"); + + // tracks # of values requested from upstream but not delivered yet via this + // .onNext(v) + volatile long outstanding; + @SuppressWarnings("rawtypes") + private AtomicLongFieldUpdater OUTSTANDING = + AtomicLongFieldUpdater.newUpdater(BufferTimeoutWithBackpressureSubscriber.class, "outstanding"); + + // indicates some thread is draining + volatile int wip; + @SuppressWarnings("rawtypes") + private AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(BufferTimeoutWithBackpressureSubscriber.class, "wip"); + + private volatile int terminated = NOT_TERMINATED; + @SuppressWarnings("rawtypes") + private AtomicIntegerFieldUpdater TERMINATED = + AtomicIntegerFieldUpdater.newUpdater(BufferTimeoutWithBackpressureSubscriber.class, "terminated"); + + final static int NOT_TERMINATED = 0; + final static int TERMINATED_WITH_SUCCESS = 1; + final static int TERMINATED_WITH_ERROR = 2; + final static int TERMINATED_WITH_CANCEL = 3; + + @Nullable + private Subscription subscription; + + private Queue queue; + + @Nullable + Throwable error; + + boolean completed; + + Disposable currentTimeoutTask; + + public BufferTimeoutWithBackpressureSubscriber( + CoreSubscriber actual, + int batchSize, + long timeSpan, + TimeUnit unit, + Scheduler.Worker timer, + Supplier bufferSupplier, + @Nullable Logger logger) { + this.actual = actual; + this.batchSize = batchSize; + this.timeSpan = timeSpan; + this.unit = unit; + this.timer = timer; + this.bufferSupplier = bufferSupplier; + this.logger = logger; + this.prefetch = batchSize << 2; + this.queue = Queues.get(prefetch).get(); + } + + private void trace(Logger logger, String msg) { + logger.trace(String.format("[%s][%s]", Thread.currentThread().getId(), msg)); + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.subscription, s)) { + this.subscription = s; + this.actual.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (logger != null) { + trace(logger, "onNext: " + t); + } + // check if terminated (cancelled / error / completed) -> discard value if so + + // increment index + // append to buffer + // drain + + if (terminated == NOT_TERMINATED) { + // assume no more deliveries than requested + if (!queue.offer(t)) { + Context ctx = currentContext(); + Throwable error = Operators.onOperatorError(this.subscription, + Exceptions.failWithOverflow(Exceptions.BACKPRESSURE_ERROR_QUEUE_FULL), + t, actual.currentContext()); + this.error = error; + if (!TERMINATED.compareAndSet(this, NOT_TERMINATED, TERMINATED_WITH_ERROR)) { + Operators.onErrorDropped(error, ctx); + return; + } + Operators.onDiscard(t, ctx); + drain(); + return; + } + + boolean shouldDrain = false; + for (;;) { + int index = this.index; + if (INDEX.compareAndSet(this, index, index + 1)) { + if (index == 0) { + try { + if (logger != null) { + trace(logger, "timerStart"); + } + currentTimeoutTask = timer.schedule(this::bufferTimedOut, + timeSpan, + unit); + } catch (RejectedExecutionException ree) { + if (logger != null) { + trace(logger, "Timer rejected for " + t); + } + Context ctx = actual.currentContext(); + Throwable error = Operators.onRejectedExecution(ree, subscription, null, t, ctx); + this.error = error; + if (!TERMINATED.compareAndSet(this, NOT_TERMINATED, TERMINATED_WITH_ERROR)) { + Operators.onDiscard(t, ctx); + Operators.onErrorDropped(error, ctx); + return; + } + if (logger != null) { + trace(logger, "Discarding upon timer rejection" + t); + } + Operators.onDiscard(t, ctx); + drain(); + return; + } + } + if ((index + 1) % batchSize == 0) { + shouldDrain = true; + } + break; + } + } + if (shouldDrain) { + if (currentTimeoutTask != null) { + // TODO: it can happen that AFTER I dispose, the timeout + // anyway kicks during/after another onNext(), the buffer is + // delivered, and THEN drain is entered -> + // it would emit a buffer that is too small potentially. + // ALSO: + // It is also possible that here we deliver the buffer, but the + // timeout is happening for a new buffer! + currentTimeoutTask.dispose(); + } + this.index = 0; + drain(); + } + } else { + if (logger != null) { + trace(logger, "Discarding onNext: " + t); + } + Operators.onDiscard(t, currentContext()); + } + } + + @Override + public void onError(Throwable t) { + // set error flag + // set terminated as error + + // drain (WIP++ ?) + + if (currentTimeoutTask != null) { + currentTimeoutTask.dispose(); + } + timer.dispose(); + + if (!TERMINATED.compareAndSet(this, NOT_TERMINATED, TERMINATED_WITH_ERROR)) { + Operators.onErrorDropped(t, currentContext()); + return; + } + this.error = t; // wip in drain will publish the error + drain(); + } + + @Override + public void onComplete() { + // set terminated as completed + // drain + if (currentTimeoutTask != null) { + currentTimeoutTask.dispose(); + } + timer.dispose(); + + if (TERMINATED.compareAndSet(this, NOT_TERMINATED, TERMINATED_WITH_SUCCESS)) { + drain(); + } + } + + @Override + public CoreSubscriber actual() { + return this.actual; + } + + @Override + public void request(long n) { + // add cap to currently requested + // if previous requested was 0 -> drain first to deliver outdated values + // if the cap increased, request more ? + + // drain + + if (Operators.validate(n)) { + if (queue.isEmpty() && terminated != NOT_TERMINATED) { + return; + } + + if (Operators.addCap(REQUESTED, this, n) == 0) { + // there was no demand before - try to fulfill the demand if there + // are buffered values + drain(); + } + + if (batchSize == Integer.MAX_VALUE || n == Long.MAX_VALUE) { + requestMore(Long.MAX_VALUE); + } else { + long requestLimit = prefetch; + if (requestLimit > outstanding) { + if (logger != null) { + trace(logger, "requestMore: " + (requestLimit - outstanding) + ", outstanding: " + outstanding); + } + requestMore(requestLimit - outstanding); + } + } + } + } + + private void requestMore(long n) { + Subscription s = this.subscription; + if (s != null) { + Operators.addCap(OUTSTANDING, this, n); + s.request(n); + } + } + + @Override + public void cancel() { + // set terminated flag + // cancel upstream subscription + // dispose timer + // drain for proper cleanup + + if (logger != null) { + trace(logger, "cancel"); + } + if (TERMINATED.compareAndSet(this, NOT_TERMINATED, TERMINATED_WITH_CANCEL)) { + if (this.subscription != null) { + this.subscription.cancel(); + } + } + if (currentTimeoutTask != null) { + currentTimeoutTask.dispose(); + } + timer.dispose(); + drain(); + } + + void bufferTimedOut() { + // called when buffer times out + + // reset index to 0 + // drain + + // TODO: try comparing against current reference and see if it was not + // cancelled -> to do this, replace Disposable timeoutTask with volatile + // and use CAS. + if (logger != null) { + trace(logger, "timerFire"); + } + this.index = 0; // if currently being drained, it means the buffer is + // delivered due to reaching the batchSize + drain(); + } + + private void drain() { + // entering this should be guarded by WIP getAndIncrement == 0 + // if we're here it means do a flush if there is downstream demand + // regardless of queue size + + // loop: + // if terminated -> check error -> deliver; else complete downstream + // if cancelled + + if (WIP.getAndIncrement(this) == 0) { + for (;;) { + int wip = this.wip; + if (logger != null) { + trace(logger, "drain. wip: " + wip); + } + if (terminated == NOT_TERMINATED) { + // is there demand? + while (flushABuffer()) { + // no-op + } + // make another spin if there's more work + } else { + if (completed) { + // if queue is empty, the discard is ignored + if (logger != null) { + trace(logger, "Discarding entire queue of " + queue.size()); + } + Operators.onDiscardQueueWithClear(queue, currentContext(), + null); + return; + } + // TODO: potentially the below can be executed twice? + if (terminated == TERMINATED_WITH_CANCEL) { + if (logger != null) { + trace(logger, "Discarding entire queue of " + queue.size()); + } + Operators.onDiscardQueueWithClear(queue, currentContext(), + null); + return; + } + while (flushABuffer()) { + // no-op + } + if (queue.isEmpty()) { + completed = true; + if (this.error != null) { + actual.onError(this.error); + } + else { + actual.onComplete(); + } + } else { + if (logger != null) { + trace(logger, "Queue not empty after termination"); + } + } + } + if (WIP.compareAndSet(this, wip, 0)) { + break; + } + } + } + } + + boolean flushABuffer() { + long requested = this.requested; + if (requested != 0) { + T element; + C buffer; + + element = queue.poll(); + if (element == null) { + // there is demand, but queue is empty + return false; + } + buffer = bufferSupplier.get(); + int i = 0; + do { + buffer.add(element); + } while ((++i < batchSize) && ((element = queue.poll()) != null)); + + if (requested != Long.MAX_VALUE) { + requested = REQUESTED.decrementAndGet(this); + } + + if (logger != null) { + trace(logger, "flush: " + buffer + ", now requested: " + requested); + } + + actual.onNext(buffer); + + if (requested != Long.MAX_VALUE) { + if (logger != null) { + trace(logger, "outstanding(" + outstanding + ") -= " + i); + } + long remaining = OUTSTANDING.addAndGet(this, -i); + if (terminated == NOT_TERMINATED) { + int replenishMark = prefetch >> 1; // TODO: create field limit instead + if (remaining < replenishMark) { + if (logger != null) { + trace(logger, "replenish: " + (prefetch - remaining) + ", outstanding: " + outstanding); + } + requestMore(prefetch - remaining); + } + } + + if (requested <= 0) { + return false; + } + } + // continue to see if there's more + return true; + } + return false; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return this.subscription; + if (key == Attr.CANCELLED) return terminated == TERMINATED_WITH_CANCEL; + if (key == Attr.TERMINATED) return terminated == TERMINATED_WITH_ERROR || terminated == TERMINATED_WITH_SUCCESS; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; + if (key == Attr.CAPACITY) return prefetch; // TODO: revise + if (key == Attr.BUFFERED) return queue.size(); + if (key == Attr.RUN_ON) return timer; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; + + return InnerOperator.super.scanUnsafe(key); + } + } + final static class BufferTimeoutSubscriber> implements InnerOperator { @@ -238,7 +682,9 @@ if (key == Attr.TERMINATED) return terminated == TERMINATED_WITH_ERROR || terminated == TERMINATED_WITH_SUCCESS; if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; if (key == Attr.CAPACITY) return batchSize; - if (key == Attr.BUFFERED) return batchSize - index; + if (key == Attr.BUFFERED) return batchSize - index; // TODO: shouldn't this + // be index instead ? as it currently stands, the returned value represents + // anticipated items left to fill buffer if completed before timeout if (key == Attr.RUN_ON) return timer; if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; @@ -248,9 +694,11 @@ @Override public void onNext(final T value) { int index; + boolean flush; for(;;){ index = this.index + 1; - if(INDEX.compareAndSet(this, index - 1, index)){ + flush = index % batchSize == 0; + if(INDEX.compareAndSet(this, index - 1, flush ? 0 : index)){ break; } } @@ -269,8 +717,7 @@ nextCallback(value); - if (this.index % batchSize == 0) { - this.index = 0; + if (flush) { if (timespanRegistration != null) { timespanRegistration.dispose(); timespanRegistration = null; @@ -314,7 +761,9 @@ } else { long requestLimit = Operators.multiplyCap(requested, batchSize); - requestMore(requestLimit - outstanding); + if (requestLimit > outstanding) { + requestMore(requestLimit - outstanding); + } } } } Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxCallable.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxCallable.java (.../FluxCallable.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxCallable.java (.../FluxCallable.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,21 +37,7 @@ @Override public void subscribe(CoreSubscriber actual) { - Operators.MonoSubscriber wrapper = new Operators.MonoSubscriber<>(actual); - actual.onSubscribe(wrapper); - - try { - T v = callable.call(); - if (v == null) { - wrapper.onComplete(); - } - else { - wrapper.complete(v); - } - } - catch (Throwable ex) { - actual.onError(Operators.onOperatorError(ex, actual.currentContext())); - } + actual.onSubscribe(new MonoCallable.MonoCallableSubscription<>(actual, callable)); } @Override Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxConcatMap.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxConcatMap.java (.../FluxConcatMap.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxConcatMap.java (.../FluxConcatMap.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -307,6 +307,7 @@ e = Exceptions.terminate(ERROR, this); if (e != TERMINATED) { actual.onError(e); + Operators.onDiscardQueueWithClear(queue, this.ctx, null); } } } @@ -413,6 +414,7 @@ if (e_ != null) { actual.onError(Operators.onOperatorError(s, e, v, this.ctx)); + Operators.onDiscardQueueWithClear(queue, this.ctx, null); return; } else { Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxContextWrite.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxContextWrite.java (.../FluxContextWrite.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxContextWrite.java (.../FluxContextWrite.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -46,7 +46,7 @@ @Override public Object scanUnsafe(Attr key) { - if (key == Scannable.Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; return super.scanUnsafe(key); } @@ -79,7 +79,7 @@ if (key == Attr.PARENT) { return s; } - if (key == Scannable.Attr.RUN_STYLE) { + if (key == Attr.RUN_STYLE) { return Attr.RunStyle.SYNC; } return InnerOperator.super.scanUnsafe(key); @@ -175,4 +175,4 @@ return qs != null ? qs.size() : 0; } } -} +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxContextWriteRestoringThreadLocals.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxContextWriteRestoringThreadLocals.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxContextWriteRestoringThreadLocals.java (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reactor.core.publisher; + +import java.util.Objects; +import java.util.function.Function; + +import io.micrometer.context.ContextSnapshot; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable.ConditionalSubscriber; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +final class FluxContextWriteRestoringThreadLocals extends FluxOperator { + + final Function doOnContext; + + FluxContextWriteRestoringThreadLocals(Flux source, + Function doOnContext) { + super(source); + this.doOnContext = Objects.requireNonNull(doOnContext, "doOnContext"); + } + + @SuppressWarnings("try") + @Override + public void subscribe(CoreSubscriber actual) { + Context c = doOnContext.apply(actual.currentContext()); + + try (ContextSnapshot.Scope ignored = ContextPropagation.setThreadLocals(c)) { + source.subscribe(new ContextWriteRestoringThreadLocalsSubscriber<>(actual, c)); + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class ContextWriteRestoringThreadLocalsSubscriber + implements ConditionalSubscriber, InnerOperator { + + final CoreSubscriber actual; + final ConditionalSubscriber actualConditional; + final Context context; + + Subscription s; + + @SuppressWarnings("unchecked") + ContextWriteRestoringThreadLocalsSubscriber(CoreSubscriber actual, Context context) { + this.actual = actual; + this.context = context; + if (actual instanceof ConditionalSubscriber) { + this.actualConditional = (ConditionalSubscriber) actual; + } + else { + this.actualConditional = null; + } + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) { + return s; + } + if (key == Attr.RUN_STYLE) { + return Attr.RunStyle.SYNC; + } + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public Context currentContext() { + return this.context; + } + + @SuppressWarnings("try") + @Override + public void onSubscribe(Subscription s) { + // This is needed, as the downstream can then switch threads, + // continue the subscription using different primitives and omit this operator + try (ContextSnapshot.Scope ignored = + ContextPropagation.setThreadLocals(actual.currentContext())) { + if (Operators.validate(this.s, s)) { + this.s = s; + actual.onSubscribe(this); + } + } + } + + @SuppressWarnings("try") + @Override + public void onNext(T t) { + // We probably ended up here from a request, which set thread locals to + // current context, but we need to clean up and restore thread locals for + // the actual subscriber downstream, as it can expect TLs to match the + // different context. + try (ContextSnapshot.Scope ignored = + ContextPropagation.setThreadLocals(actual.currentContext())) { + actual.onNext(t); + } + } + + @SuppressWarnings("try") + @Override + public boolean tryOnNext(T t) { + try (ContextSnapshot.Scope ignored = + ContextPropagation.setThreadLocals(actual.currentContext())) { + if (actualConditional != null) { + return actualConditional.tryOnNext(t); + } + actual.onNext(t); + return true; + } + } + + @SuppressWarnings("try") + @Override + public void onError(Throwable t) { + try (ContextSnapshot.Scope ignored = + ContextPropagation.setThreadLocals(actual.currentContext())) { + actual.onError(t); + } + } + + @SuppressWarnings("try") + @Override + public void onComplete() { + try (ContextSnapshot.Scope ignored = + ContextPropagation.setThreadLocals(actual.currentContext())) { + actual.onComplete(); + } + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @SuppressWarnings("try") + @Override + public void request(long n) { + try (ContextSnapshot.Scope ignored = + ContextPropagation.setThreadLocals(context)) { + s.request(n); + } + } + + @SuppressWarnings("try") + @Override + public void cancel() { + try (ContextSnapshot.Scope ignored = + ContextPropagation.setThreadLocals(context)) { + s.cancel(); + } + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxCreate.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxCreate.java (.../FluxCreate.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxCreate.java (.../FluxCreate.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,6 +37,7 @@ import reactor.util.annotation.Nullable; import reactor.util.concurrent.Queues; import reactor.util.context.Context; +import reactor.util.context.ContextView; /** * Provides a multi-valued sink API for a callback that is called for each individual @@ -57,7 +58,7 @@ final CreateMode createMode; FluxCreate(Consumer> source, - FluxSink.OverflowStrategy backpressure, + OverflowStrategy backpressure, CreateMode createMode) { this.source = Objects.requireNonNull(source, "source"); this.backpressure = Objects.requireNonNull(backpressure, "backpressure"); @@ -138,11 +139,17 @@ } @Override + @Deprecated public Context currentContext() { return sink.currentContext(); } @Override + public ContextView contextView() { + return sink.contextView(); + } + + @Override public FluxSink next(T t) { Objects.requireNonNull(t, "t is null in sink.next(t)"); if (sink.isTerminated() || done) { @@ -261,7 +268,7 @@ @Override public FluxSink onRequest(LongConsumer consumer) { - sink.onRequest(consumer, consumer, sink.requested); + sink.onPushPullRequest(consumer); return this; } @@ -327,11 +334,17 @@ } @Override + @Deprecated public Context currentContext() { return sink.currentContext(); } @Override + public ContextView contextView() { + return sink.contextView(); + } + + @Override public Object scanUnsafe(Attr key) { return serializedSink != null ? serializedSink.scanUnsafe(key) : baseSink.scanUnsafe(key); @@ -396,6 +409,8 @@ static final Disposable TERMINATED = OperatorDisposables.DISPOSED; static final Disposable CANCELLED = Disposables.disposed(); + static final LongConsumer NOOP_CONSUMER = n -> {}; + final CoreSubscriber actual; final Context ctx; @@ -421,16 +436,25 @@ BaseSink(CoreSubscriber actual) { this.actual = actual; this.ctx = actual.currentContext(); + REQUESTED.lazySet(this, Long.MIN_VALUE); } @Override + @Deprecated public Context currentContext() { //we cache the context for hooks purposes, but this forces to go through the // chain when queried for context, in case downstream can update the Context... return actual.currentContext(); } @Override + public ContextView contextView() { + //we cache the context for hooks purposes, but this forces to go through the + // chain when queried for context, in case downstream can update the Context... + return actual.currentContext(); + } + + @Override public void complete() { if (isTerminated()) { return; @@ -479,7 +503,7 @@ @Override public long requestedFromDownstream() { - return requested; + return requested & Long.MAX_VALUE; } void onCancel() { @@ -498,12 +522,15 @@ @Override public final void request(long n) { if (Operators.validate(n)) { - Operators.addCap(REQUESTED, this, n); + long s = addCap(this, n); - LongConsumer consumer = requestConsumer; - if (n > 0 && consumer != null && !isCancelled()) { - consumer.accept(n); + if (hasRequestConsumer(s)) { + LongConsumer consumer = requestConsumer; + if (!isCancelled()) { + consumer.accept(n); + } } + onRequestedFromDownstream(); } } @@ -520,20 +547,29 @@ @Override public FluxSink onRequest(LongConsumer consumer) { Objects.requireNonNull(consumer, "onRequest"); - onRequest(consumer, n -> { - }, Long.MAX_VALUE); + onPushRequest(consumer); return this; } - protected void onRequest(LongConsumer initialRequestConsumer, - LongConsumer requestConsumer, - long value) { + protected void onPushRequest(LongConsumer initialRequestConsumer) { + if (!REQUEST_CONSUMER.compareAndSet(this, null, NOOP_CONSUMER)) { + throw new IllegalStateException( + "A consumer has already been assigned to consume requests"); + } + + // do not change real flag since real consumer is technically absent + initialRequestConsumer.accept(Long.MAX_VALUE); + } + + protected void onPushPullRequest(LongConsumer requestConsumer) { if (!REQUEST_CONSUMER.compareAndSet(this, null, requestConsumer)) { throw new IllegalStateException( "A consumer has already been assigned to consume requests"); } - else if (value > 0) { - initialRequestConsumer.accept(value); + + long initialRequest = markRequestConsumerSet(this); + if (initialRequest > 0) { + requestConsumer.accept(initialRequest); } } @@ -586,7 +622,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.TERMINATED) return disposable == TERMINATED; if (key == Attr.CANCELLED) return disposable == CANCELLED; - if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requestedFromDownstream(); if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; return InnerProducer.super.scanUnsafe(key); @@ -596,6 +632,54 @@ public String toString() { return "FluxSink"; } + + static void produced(BaseSink instance, long toSub) { + long s, r, u; + do { + s = instance.requested; + r = s & Long.MAX_VALUE; + if (r == 0 || r == Long.MAX_VALUE) { + return; + } + u = Operators.subOrZero(r, toSub); + } while (!REQUESTED.compareAndSet(instance, s, u | (s & Long.MIN_VALUE))); + } + + + static long addCap(BaseSink instance, long toAdd) { + long r, u, s; + for (;;) { + s = instance.requested; + r = s & Long.MAX_VALUE; + if (r == Long.MAX_VALUE) { + return s; + } + u = Operators.addCap(r, toAdd); + if (REQUESTED.compareAndSet(instance, s, u | (s & Long.MIN_VALUE))) { + return s; + } + } + } + + static long markRequestConsumerSet(BaseSink instance) { + long u, s; + for (;;) { + s = instance.requested; + + if (hasRequestConsumer(s)) { + return s; + } + + u = s & Long.MAX_VALUE; + if (REQUESTED.compareAndSet(instance, s, u)) { + return u; + } + } + } + + static boolean hasRequestConsumer(long requestedState) { + return (requestedState & Long.MIN_VALUE) == 0; + } } static final class IgnoreSink extends BaseSink { @@ -618,8 +702,9 @@ actual.onNext(t); for (; ; ) { - long r = requested; - if (r == 0L || REQUESTED.compareAndSet(this, r, r - 1)) { + long s = requested; + long r = s & Long.MAX_VALUE; + if (r == 0L || REQUESTED.compareAndSet(this, s, (r - 1) | (s & Long.MIN_VALUE))) { return this; } } @@ -644,9 +729,9 @@ return this; } - if (requested != 0) { + if (requestedFromDownstream() != 0) { actual.onNext(t); - Operators.produced(REQUESTED, this, 1); + produced(this, 1); } else { onOverflow(); @@ -755,7 +840,7 @@ final Queue q = queue; for (; ; ) { - long r = requested; + long r = requestedFromDownstream(); long e = 0L; while (e != r) { @@ -823,7 +908,7 @@ } if (e != 0) { - Operators.produced(REQUESTED, this, e); + produced(this, e); } if (WIP.decrementAndGet(this) == 0) { @@ -915,7 +1000,7 @@ final AtomicReference q = queue; for (; ; ) { - long r = requested; + long r = requestedFromDownstream(); long e = 0L; while (e != r) { @@ -985,7 +1070,7 @@ } if (e != 0) { - Operators.produced(REQUESTED, this, e); + produced(this, e); } if (WIP.decrementAndGet(this) == 0) { @@ -1040,4 +1125,4 @@ } } } -} +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxDefaultIfEmpty.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxDefaultIfEmpty.java (.../FluxDefaultIfEmpty.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxDefaultIfEmpty.java (.../FluxDefaultIfEmpty.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,10 +17,9 @@ package reactor.core.publisher; import java.util.Objects; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; -import org.reactivestreams.Subscription; import reactor.core.CoreSubscriber; -import reactor.core.Fuseable; import reactor.util.annotation.Nullable; /** @@ -50,75 +49,120 @@ } static final class DefaultIfEmptySubscriber - extends Operators.MonoSubscriber { + extends Operators.BaseFluxToMonoOperator { - Subscription s; + boolean done; boolean hasValue; - DefaultIfEmptySubscriber(CoreSubscriber actual, T value) { + volatile T fallbackValue; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater FALLBACK_VALUE = + AtomicReferenceFieldUpdater.newUpdater(DefaultIfEmptySubscriber.class, Object.class, "fallbackValue"); + + DefaultIfEmptySubscriber(CoreSubscriber actual, T fallbackValue) { super(actual); - //noinspection deprecation - this.value = value; //we write once, setValue() is NO-OP + FALLBACK_VALUE.lazySet(this, fallbackValue); } @Override @Nullable public Object scanUnsafe(Attr key) { - if (key == Attr.PARENT) return s; - if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.TERMINATED) return done; return super.scanUnsafe(key); } @Override public void request(long n) { - super.request(n); + if (!hasRequest) { + hasRequest = true; + + final int state = this.state; + + if (state != 1 && STATE.compareAndSet(this, state, state | 1)) { + if (state > 1) { + final T fallbackValue = this.fallbackValue; + if (fallbackValue != null && FALLBACK_VALUE.compareAndSet(this, + fallbackValue, + null)) { + // completed before request means source was empty + actual.onNext(fallbackValue); + actual.onComplete(); + } + return; + } + } + } + s.request(n); } @Override public void cancel() { super.cancel(); - s.cancel(); - } - @Override - public void onSubscribe(Subscription s) { - if (Operators.validate(this.s, s)) { - this.s = s; - - actual.onSubscribe(this); + final T fallbackValue = this.fallbackValue; + if (fallbackValue != null && FALLBACK_VALUE.compareAndSet(this, fallbackValue, null)) { + Operators.onDiscard(fallbackValue, actual.currentContext()); } } @Override public void onNext(T t) { if (!hasValue) { hasValue = true; + + final T fallbackValue = this.fallbackValue; + if (fallbackValue != null && FALLBACK_VALUE.compareAndSet(this, fallbackValue, null)) { + Operators.onDiscard(fallbackValue, actual.currentContext()); + } } actual.onNext(t); } @Override public void onComplete() { - if (hasValue) { - actual.onComplete(); - } else { - complete(this.value); + if (done) { + return; } + + done = true; + + if (!hasValue) { + completePossiblyEmpty(); + + return; + } + + actual.onComplete(); } @Override - public void setValue(T value) { - // value is constant. writes from the base class are redundant, and the constant - // would always be visible in cancel(), so it will safely be discarded. + public void onError(Throwable t) { + if (done) { + return; + } + + done = true; + if (!hasValue) { + final T fallbackValue = this.fallbackValue; + if (fallbackValue != null && FALLBACK_VALUE.compareAndSet(this, fallbackValue, null)) { + Operators.onDiscard(t, actual.currentContext()); + } + } + + actual.onError(t); } @Override - public int requestFusion(int requestedMode) { - return Fuseable.NONE; // prevent fusion because of the upstream + T accumulatedValue() { + final T fallbackValue = this.fallbackValue; + if (fallbackValue != null && FALLBACK_VALUE.compareAndSet(this, fallbackValue, null)) { + return fallbackValue; + } + return null; } } } Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxDelaySubscription.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxDelaySubscription.java (.../FluxDelaySubscription.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxDelaySubscription.java (.../FluxDelaySubscription.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -68,7 +68,7 @@ static final class DelaySubscriptionOtherSubscriber extends Operators.DeferredSubscription implements InnerOperator { - final Consumer> source; + final Consumer> source; final CoreSubscriber actual; @@ -77,7 +77,7 @@ boolean done; DelaySubscriptionOtherSubscriber(CoreSubscriber actual, - Consumer> source) { + Consumer> source) { this.actual = actual; this.source = source; } @@ -199,4 +199,4 @@ actual.onComplete(); } } -} +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxDoFinally.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxDoFinally.java (.../FluxDoFinally.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxDoFinally.java (.../FluxDoFinally.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,18 +45,8 @@ final Consumer onFinally; @SuppressWarnings("unchecked") - static CoreSubscriber createSubscriber( - CoreSubscriber s, Consumer onFinally, - boolean fuseable) { + static CoreSubscriber createSubscriber(CoreSubscriber s, Consumer onFinally) { - if (fuseable) { - if(s instanceof ConditionalSubscriber) { - return new DoFinallyFuseableConditionalSubscriber<>( - (ConditionalSubscriber) s, onFinally); - } - return new DoFinallyFuseableSubscriber<>(s, onFinally); - } - if (s instanceof ConditionalSubscriber) { return new DoFinallyConditionalSubscriber<>((ConditionalSubscriber) s, onFinally); } @@ -70,7 +60,7 @@ @Override public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { - return createSubscriber(actual, onFinally, false); + return createSubscriber(actual, onFinally); } @Override @@ -87,15 +77,12 @@ volatile int once; + @SuppressWarnings("rawtypes") static final AtomicIntegerFieldUpdater ONCE = AtomicIntegerFieldUpdater.newUpdater(DoFinallySubscriber.class, "once"); - QueueSubscription qs; - Subscription s; - boolean syncFused; - DoFinallySubscriber(CoreSubscriber actual, Consumer onFinally) { this.actual = actual; this.onFinally = onFinally; @@ -112,14 +99,10 @@ return InnerOperator.super.scanUnsafe(key); } - @SuppressWarnings("unchecked") @Override public void onSubscribe(Subscription s) { if (Operators.validate(this.s, s)) { this.s = s; - if (s instanceof QueueSubscription) { - this.qs = (QueueSubscription)s; - } actual.onSubscribe(this); } @@ -175,57 +158,6 @@ } - static class DoFinallyFuseableSubscriber extends DoFinallySubscriber - implements Fuseable, QueueSubscription { - - DoFinallyFuseableSubscriber(CoreSubscriber actual, Consumer onFinally) { - super(actual, onFinally); - } - - @Override - public int requestFusion(int mode) { - QueueSubscription qs = this.qs; - if (qs != null && (mode & Fuseable.THREAD_BARRIER) == 0) { - int m = qs.requestFusion(mode); - if (m != Fuseable.NONE) { - syncFused = m == Fuseable.SYNC; - } - return m; - } - return Fuseable.NONE; - } - - @Override - public void clear() { - if (qs != null) { - qs.clear(); - } - } - - @Override - public boolean isEmpty() { - return qs == null || qs.isEmpty(); - } - - @Override - @Nullable - public T poll() { - if (qs == null) { - return null; - } - T v = qs.poll(); - if (v == null && syncFused) { - runFinally(SignalType.ON_COMPLETE); - } - return v; - } - - @Override - public int size() { - return qs == null ? 0 : qs.size(); - } - } - static final class DoFinallyConditionalSubscriber extends DoFinallySubscriber implements ConditionalSubscriber { @@ -240,19 +172,4 @@ return ((ConditionalSubscriber)actual).tryOnNext(t); } } - - static final class DoFinallyFuseableConditionalSubscriber extends DoFinallyFuseableSubscriber - implements ConditionalSubscriber { - - DoFinallyFuseableConditionalSubscriber(ConditionalSubscriber actual, - Consumer onFinally) { - super(actual, onFinally); - } - - @Override - @SuppressWarnings("unchecked") - public boolean tryOnNext(T t) { - return ((ConditionalSubscriber)actual).tryOnNext(t); - } - } } Fisheye: Tag c4ce08dc0aae7d9da822088a3d5710484f6b0402 refers to a dead (removed) revision in file `3rdParty_sources/reactor/reactor/core/publisher/FluxDoFinallyFuseable.java'. Fisheye: No comparison available. Pass `N' to diff? Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxDoOnEach.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxDoOnEach.java (.../FluxDoOnEach.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxDoOnEach.java (.../FluxDoOnEach.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -265,23 +265,33 @@ static class DoOnEachFuseableSubscriber extends DoOnEachSubscriber implements Fuseable, Fuseable.QueueSubscription { - boolean syncFused; + int fusionMode; DoOnEachFuseableSubscriber(CoreSubscriber actual, Consumer> onSignal, boolean isMono) { super(actual, onSignal, isMono); } @Override + public void onNext(T t) { + if (this.fusionMode == Fuseable.ASYNC) { + actual.onNext(null); + return; + } + super.onNext(t); + } + + @Override public int requestFusion(int mode) { QueueSubscription qs = this.qs; if (qs != null && (mode & Fuseable.THREAD_BARRIER) == 0) { int m = qs.requestFusion(mode); - if (m != Fuseable.NONE) { - syncFused = m == Fuseable.SYNC; + if (m == Fuseable.SYNC || m == Fuseable.ASYNC) { + this.fusionMode = m; + return m; } - return m; } + this.fusionMode = NONE; return Fuseable.NONE; } @@ -302,7 +312,7 @@ return null; } T v = qs.poll(); - if (v == null && syncFused) { + if (v == null && this.fusionMode == SYNC) { state = STATE_DONE; try { onSignal.accept(Signal.complete(cachedContext)); Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxErrorSupplied.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxErrorSupplied.java (.../FluxErrorSupplied.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxErrorSupplied.java (.../FluxErrorSupplied.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -25,7 +25,7 @@ /** * Emits a generated {@link Throwable} instance to Subscribers, lazily generated via a - * provided {@link java.util.function.Supplier}. + * provided {@link Supplier}. * * @param the value type * @@ -59,4 +59,4 @@ if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; return null; } -} +} \ No newline at end of file Fisheye: Tag c4ce08dc0aae7d9da822088a3d5710484f6b0402 refers to a dead (removed) revision in file `3rdParty_sources/reactor/reactor/core/publisher/FluxExtensions.kt'. Fisheye: No comparison available. Pass `N' to diff? Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxFilter.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxFilter.java (.../FluxFilter.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxFilter.java (.../FluxFilter.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -61,7 +61,7 @@ static final class FilterSubscriber implements InnerOperator, - Fuseable.ConditionalSubscriber { + ConditionalSubscriber { final CoreSubscriber actual; final Context ctx; @@ -194,9 +194,9 @@ static final class FilterConditionalSubscriber implements InnerOperator, - Fuseable.ConditionalSubscriber { + ConditionalSubscriber { - final Fuseable.ConditionalSubscriber actual; + final ConditionalSubscriber actual; final Context ctx; final Predicate predicate; @@ -205,7 +205,7 @@ boolean done; - FilterConditionalSubscriber(Fuseable.ConditionalSubscriber actual, + FilterConditionalSubscriber(ConditionalSubscriber actual, Predicate predicate) { this.actual = actual; this.ctx = actual.currentContext(); @@ -327,4 +327,4 @@ } } -} +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxFlatMap.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxFlatMap.java (.../FluxFlatMap.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxFlatMap.java (.../FluxFlatMap.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -416,6 +416,7 @@ onError(Operators.onOperatorError(s, e_, t, ctx)); } Operators.onDiscard(t, ctx); + tryEmitScalar(null); return; } tryEmitScalar(v); Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxFlattenIterable.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxFlattenIterable.java (.../FluxFlattenIterable.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxFlattenIterable.java (.../FluxFlattenIterable.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,13 +16,15 @@ package reactor.core.publisher; -import java.util.Iterator; +import java.util.NoSuchElementException; import java.util.Objects; import java.util.Queue; +import java.util.Spliterator; import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.concurrent.atomic.AtomicLongFieldUpdater; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; @@ -81,12 +83,12 @@ return null; } - Iterator it; + Spliterator sp; boolean knownToBeFinite; try { Iterable iter = mapper.apply(v); - it = iter.iterator(); - knownToBeFinite = FluxIterable.checkFinite(iter); + sp = iter.spliterator(); + knownToBeFinite = FluxIterable.checkFinite(sp); } catch (Throwable ex) { Context ctx = actual.currentContext(); @@ -102,7 +104,7 @@ } // TODO return subscriber (tail-call optimization)? - FluxIterable.subscribe(actual, it, knownToBeFinite); + FluxIterable.subscribe(actual, sp, knownToBeFinite); return null; } return new FlattenIterableSubscriber<>(actual, @@ -118,7 +120,7 @@ } static final class FlattenIterableSubscriber - implements InnerOperator, QueueSubscription { + implements InnerOperator, QueueSubscription, Consumer { final CoreSubscriber actual; @@ -159,9 +161,13 @@ "error"); @Nullable - Iterator current; + Spliterator current; boolean currentKnownToBeFinite; + boolean valueReady = false; + + R nextElement; + int consumed; int fusionMode; @@ -268,6 +274,29 @@ } @Override + public void accept(R t) { + valueReady = true; + nextElement = t; + } + + boolean hasNext(Spliterator spliterator) { + if (!valueReady) + spliterator.tryAdvance(this); + return valueReady; + } + + R next(Spliterator spliterator) { + if (!valueReady && !hasNext(spliterator)) + throw new NoSuchElementException(); + else { + valueReady = false; + R t = nextElement; + nextElement = null; + return t; + } + } + + @Override public void request(long n) { if (Operators.validate(n)) { Operators.addCap(REQUESTED, this, n); @@ -285,6 +314,7 @@ if (WIP.getAndIncrement(this) == 0) { Context context = actual.currentContext(); Operators.onDiscardQueueWithClear(queue, context, null); + Operators.onDiscard(nextElement, context); Operators.onDiscardMultiple(current, currentKnownToBeFinite, context); } } @@ -301,12 +331,12 @@ final Queue q = queue; int missed = 1; - Iterator it = current; + Spliterator sp = current; boolean itFinite = currentKnownToBeFinite; for (; ; ) { - if (it == null) { + if (sp == null) { if (cancelled) { Operators.onDiscardQueueWithClear(q, actual.currentContext(), null); @@ -345,17 +375,17 @@ if (!empty) { Iterable iterable; - boolean b; + boolean isEmpty; try { iterable = mapper.apply(t); - it = iterable.iterator(); - itFinite = FluxIterable.checkFinite(iterable); + sp = iterable.spliterator(); + itFinite = FluxIterable.checkFinite(sp); - b = it.hasNext(); + isEmpty = itFinite ? sp.estimateSize() == 0 : !hasNext(sp); } catch (Throwable exc) { - it = null; + sp = null; itFinite = false; //reset explicitly Context ctx = actual.currentContext(); Throwable e_ = Operators.onNextError(t, exc, ctx, s); @@ -366,8 +396,8 @@ continue; } - if (!b) { - it = null; + if (isEmpty) { + sp = null; itFinite = false; //reset explicitly int c = consumed + 1; if (c == limit) { @@ -382,7 +412,7 @@ } } - if (it != null) { + if (sp != null) { long r = requested; long e = 0L; @@ -391,7 +421,8 @@ resetCurrent(); final Context context = actual.currentContext(); Operators.onDiscardQueueWithClear(q, context, null); - Operators.onDiscardMultiple(it, itFinite, context); + Operators.onDiscard(nextElement, context); + Operators.onDiscardMultiple(sp, itFinite, context); return; } @@ -401,15 +432,16 @@ resetCurrent(); final Context context = actual.currentContext(); Operators.onDiscardQueueWithClear(q, context, null); - Operators.onDiscardMultiple(it, itFinite, context); + Operators.onDiscard(nextElement, context); + Operators.onDiscardMultiple(sp, itFinite, context); a.onError(ex); return; } R v; try { - v = Objects.requireNonNull(it.next(), + v = Objects.requireNonNull(next(sp), "iterator returned null"); } catch (Throwable exc) { @@ -424,7 +456,8 @@ resetCurrent(); final Context context = actual.currentContext(); Operators.onDiscardQueueWithClear(q, context, null); - Operators.onDiscardMultiple(it, itFinite, context); + Operators.onDiscard(nextElement, context); + Operators.onDiscardMultiple(sp, itFinite, context); return; } @@ -433,7 +466,7 @@ boolean b; try { - b = it.hasNext(); + b = hasNext(sp); } catch (Throwable exc) { onError(Operators.onOperatorError(s, exc, @@ -450,7 +483,7 @@ else { consumed = c; } - it = null; + sp = null; itFinite = false; resetCurrent(); break; @@ -462,7 +495,8 @@ resetCurrent(); final Context context = actual.currentContext(); Operators.onDiscardQueueWithClear(q, context, null); - Operators.onDiscardMultiple(it, itFinite, context); + Operators.onDiscard(nextElement, context); + Operators.onDiscardMultiple(sp, itFinite, context); return; } @@ -472,13 +506,14 @@ resetCurrent(); final Context context = actual.currentContext(); Operators.onDiscardQueueWithClear(q, context, null); - Operators.onDiscardMultiple(it, itFinite, context); + Operators.onDiscard(nextElement, context); + Operators.onDiscardMultiple(sp, itFinite, context); a.onError(ex); return; } boolean d = done; - boolean empty = q.isEmpty() && it == null; + boolean empty = q.isEmpty() && sp == null; if (d && empty) { resetCurrent(); @@ -493,12 +528,12 @@ } } - if (it == null) { + if (sp == null) { continue; } } - current = it; + current = sp; currentKnownToBeFinite = itFinite; missed = WIP.addAndGet(this, -missed); if (missed == 0) { @@ -511,11 +546,11 @@ final Subscriber a = actual; int missed = 1; - Iterator it = current; + Spliterator sp = current; boolean itFinite = currentKnownToBeFinite; for (; ; ) { - if (it == null) { + if (sp == null) { if (cancelled) { Operators.onDiscardQueueWithClear(queue, actual.currentContext(), null); @@ -546,14 +581,14 @@ if (!empty) { Iterable iterable; - boolean b; + boolean isEmpty; try { iterable = mapper.apply(t); - it = iterable.iterator(); - itFinite = FluxIterable.checkFinite(iterable); + sp = iterable.spliterator(); + itFinite = FluxIterable.checkFinite(sp); - b = it.hasNext(); + isEmpty = itFinite ? sp.estimateSize() == 0 : !hasNext(sp); } catch (Throwable exc) { resetCurrent(); @@ -569,15 +604,15 @@ continue; } - if (!b) { - it = null; + if (isEmpty) { + sp = null; itFinite = false; continue; } } } - if (it != null) { + if (sp != null) { long r = requested; long e = 0L; @@ -586,14 +621,15 @@ resetCurrent(); final Context context = actual.currentContext(); Operators.onDiscardQueueWithClear(queue, context, null); - Operators.onDiscardMultiple(it, itFinite, context); + Operators.onDiscard(nextElement, context); + Operators.onDiscardMultiple(sp, itFinite, context); return; } R v; try { - v = Objects.requireNonNull(it.next(), "iterator returned null"); + v = Objects.requireNonNull(next(sp), "iterator returned null"); } catch (Throwable exc) { resetCurrent(); @@ -607,7 +643,8 @@ resetCurrent(); final Context context = actual.currentContext(); Operators.onDiscardQueueWithClear(queue, context, null); - Operators.onDiscardMultiple(it, itFinite, context); + Operators.onDiscard(nextElement, context); + Operators.onDiscardMultiple(sp, itFinite, context); return; } @@ -616,7 +653,7 @@ boolean b; try { - b = it.hasNext(); + b = hasNext(sp); } catch (Throwable exc) { resetCurrent(); @@ -625,7 +662,7 @@ } if (!b) { - it = null; + sp = null; itFinite = false; resetCurrent(); break; @@ -637,12 +674,13 @@ resetCurrent(); final Context context = actual.currentContext(); Operators.onDiscardQueueWithClear(queue, context, null); - Operators.onDiscardMultiple(it, itFinite, context); + Operators.onDiscard(nextElement, context); + Operators.onDiscardMultiple(sp, itFinite, context); return; } boolean d = done; - boolean empty = queue.isEmpty() && it == null; + boolean empty = queue.isEmpty() && sp == null; if (d && empty) { resetCurrent(); @@ -657,12 +695,12 @@ } } - if (it == null) { + if (sp == null) { continue; } } - current = it; + current = sp; currentKnownToBeFinite = itFinite; missed = WIP.addAndGet(this, -missed); if (missed == 0) { @@ -690,27 +728,28 @@ @Override public void clear() { final Context context = actual.currentContext(); + Operators.onDiscard(nextElement, context); Operators.onDiscardMultiple(current, currentKnownToBeFinite, context); resetCurrent(); Operators.onDiscardQueueWithClear(queue, context, null); } @Override public boolean isEmpty() { - Iterator it = current; - if (it != null) { - return !it.hasNext(); + Spliterator sp = current; + if (sp != null) { + return !hasNext(sp); } return queue.isEmpty(); // estimate } @Override @Nullable public R poll() { - Iterator it = current; + Spliterator sp = current; boolean itFinite; for (; ; ) { - if (it == null) { + if (sp == null) { T v = queue.poll(); if (v == null) { return null; @@ -719,28 +758,28 @@ Iterable iterable; try { iterable = mapper.apply(v); - it = iterable.iterator(); - itFinite = FluxIterable.checkFinite(iterable); + sp = iterable.spliterator(); + itFinite = FluxIterable.checkFinite(sp); } catch (Throwable error) { Operators.onDiscard(v, actual.currentContext()); throw error; } - if (!it.hasNext()) { + if (!hasNext(sp)) { continue; } - current = it; + current = sp; currentKnownToBeFinite = itFinite; } - else if (!it.hasNext()) { - it = null; + else if (!hasNext(sp)) { + sp = null; continue; } - R r = Objects.requireNonNull(it.next(), "iterator returned null"); + R r = Objects.requireNonNull(next(sp), "iterator returned null"); - if (!it.hasNext()) { + if (!hasNext(sp)) { resetCurrent(); } Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxGenerate.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxGenerate.java (.../FluxGenerate.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxGenerate.java (.../FluxGenerate.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,7 @@ import reactor.core.Fuseable; import reactor.util.annotation.Nullable; import reactor.util.context.Context; +import reactor.util.context.ContextView; /** * Generate signals one-by-one via a function callback. @@ -120,20 +121,25 @@ AtomicLongFieldUpdater.newUpdater(GenerateSubscription.class, "requested"); GenerateSubscription(CoreSubscriber actual, S state, - BiFunction, S> generator, Consumer stateConsumer) { + BiFunction, S> generator, Consumer stateConsumer) { this.actual = actual; this.state = state; this.generator = generator; this.stateConsumer = stateConsumer; } @Override + @Deprecated public Context currentContext() { return actual.currentContext(); } @Override + public ContextView contextView() { + return actual.currentContext(); + } + + @Override @Nullable public Object scanUnsafe(Attr key) { if (key == Attr.TERMINATED) return terminate; @@ -290,6 +296,7 @@ if (n == e) { state = s; n = REQUESTED.addAndGet(this, -e); + e = 0L; if (n == 0L) { return; } Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxGroupBy.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxGroupBy.java (.../FluxGroupBy.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxGroupBy.java (.../FluxGroupBy.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -718,7 +718,7 @@ drain(); } else { - actual.onError(new IllegalStateException( + Operators.error(actual, new IllegalStateException( "GroupedFlux allows only one Subscriber")); } } Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxHandle.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxHandle.java (.../FluxHandle.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxHandle.java (.../FluxHandle.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,10 +20,12 @@ import java.util.function.BiConsumer; import org.reactivestreams.Subscription; + import reactor.core.CoreSubscriber; import reactor.core.Fuseable; import reactor.util.annotation.Nullable; import reactor.util.context.Context; +import reactor.util.context.ContextView; /** * Maps the values of the source publisher one-on-one via a handler function as long as the handler function result is @@ -43,12 +45,14 @@ @Override public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + BiConsumer> handler2 = ContextPropagationSupport.shouldRestoreThreadLocalsInSomeOperators() ? + ContextPropagation.contextRestoreForHandle(this.handler, actual::currentContext) : this.handler; if (actual instanceof Fuseable.ConditionalSubscriber) { @SuppressWarnings("unchecked") Fuseable.ConditionalSubscriber cs = (Fuseable.ConditionalSubscriber) actual; - return new HandleConditionalSubscriber<>(cs, handler); + return new HandleConditionalSubscriber<>(cs, handler2); } - return new HandleSubscriber<>(actual, handler); + return new HandleSubscriber<>(actual, handler2); } @Override @@ -88,11 +92,17 @@ } @Override + @Deprecated public Context currentContext() { return actual.currentContext(); } @Override + public ContextView contextView() { + return actual.currentContext(); + } + + @Override public void onNext(T t) { if (done) { Operators.onNextDropped(t, actual.currentContext()); @@ -287,11 +297,17 @@ } @Override + @Deprecated public Context currentContext() { return actual.currentContext(); } @Override + public ContextView contextView() { + return actual.currentContext(); + } + + @Override public void onSubscribe(Subscription s) { if (Operators.validate(this.s, s)) { this.s = s; Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxHandleFuseable.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxHandleFuseable.java (.../FluxHandleFuseable.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxHandleFuseable.java (.../FluxHandleFuseable.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,11 +20,13 @@ import java.util.function.BiConsumer; import org.reactivestreams.Subscription; + import reactor.core.CoreSubscriber; import reactor.core.Exceptions; import reactor.core.Fuseable; import reactor.util.annotation.Nullable; import reactor.util.context.Context; +import reactor.util.context.ContextView; /** * Maps the values of the source publisher one-on-one via a handler function. @@ -56,12 +58,14 @@ @Override public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + BiConsumer> handler2 = ContextPropagationSupport.shouldRestoreThreadLocalsInSomeOperators() ? + ContextPropagation.contextRestoreForHandle(this.handler, actual::currentContext) : this.handler; if (actual instanceof ConditionalSubscriber) { @SuppressWarnings("unchecked") ConditionalSubscriber cs = (ConditionalSubscriber) actual; - return new HandleFuseableConditionalSubscriber<>(cs, handler); + return new HandleFuseableConditionalSubscriber<>(cs, handler2); } - return new HandleFuseableSubscriber<>(actual, handler); + return new HandleFuseableSubscriber<>(actual, handler2); } @Override @@ -94,11 +98,17 @@ } @Override + @Deprecated public Context currentContext() { return actual.currentContext(); } @Override + public ContextView contextView() { + return actual.currentContext(); + } + + @Override public boolean tryOnNext(T t) { if (done) { Operators.onNextDropped(t, actual.currentContext()); @@ -445,10 +455,16 @@ } @Override + @Deprecated public Context currentContext() { return actual.currentContext(); } + @Override + public ContextView contextView() { + return actual.currentContext(); + } + @SuppressWarnings("unchecked") @Override public void onSubscribe(Subscription s) { Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxIndexFuseable.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxIndexFuseable.java (.../FluxIndexFuseable.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxIndexFuseable.java (.../FluxIndexFuseable.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -26,7 +26,7 @@ import reactor.util.function.Tuple2; /** - * A {@link reactor.core.Fuseable} version of {@link FluxIndex}, an + * A {@link Fuseable} version of {@link FluxIndex}, an * operator that tags the values it passes through with their index in the original * sequence, either as their natural long index (0-based) or as a customized index * by way of a user-provided {@link BiFunction}. The resulting sequence is one of @@ -367,4 +367,4 @@ return InnerOperator.super.scanUnsafe(key); } } -} +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxIterable.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxIterable.java (.../FluxIterable.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxIterable.java (.../FluxIterable.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,10 +17,11 @@ package reactor.core.publisher; import java.util.Collection; -import java.util.Iterator; +import java.util.NoSuchElementException; import java.util.Objects; import java.util.Spliterator; import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.function.Consumer; import org.reactivestreams.Subscriber; @@ -31,8 +32,8 @@ import reactor.util.function.Tuple2; /** - * Emits the contents of an Iterable source. Attempt to discard remainder of a source - * in case of error / cancellation, but uses the {@link Spliterator} API to try and detect + * Emits the contents of an Iterable source via its {@link Spliterator} successor. Attempt to discard remainder of a source + * in case of error / cancellation, uses the {@link Spliterator#characteristics()} API to determine * infinite sources (so that said discarding doesn't loop infinitely). * * @param the value type @@ -46,45 +47,40 @@ * finite, which implies forEachRemaining type of iteration can be done to discard unemitted * values (in case of cancellation or error). *

      - * A {@link Collection} is assumed to be finite, and for other iterables the {@link Spliterator} - * {@link Spliterator#SIZED} characteristic is looked for. + * The {@link Spliterator#SIZED} characteristic is looked for. * - * @param iterable the {@link Iterable} to check. - * @param - * @return true if the {@link Iterable} can confidently classified as finite, false if not finite/unsure + * @param spliterator the {@link Spliterator} to check. + * @param values type + * @return true if the {@link Spliterator} can confidently classified as finite, false if not finite/unsure */ - static boolean checkFinite(Iterable iterable) { - return iterable instanceof Collection || iterable.spliterator().hasCharacteristics(Spliterator.SIZED); + static boolean checkFinite(Spliterator spliterator) { + return spliterator.hasCharacteristics(Spliterator.SIZED); } final Iterable iterable; @Nullable private final Runnable onClose; - FluxIterable(Iterable iterable, @Nullable Runnable onClose) { + FluxIterable(Iterable iterable) { this.iterable = Objects.requireNonNull(iterable, "iterable"); - this.onClose = onClose; + this.onClose = null; } - FluxIterable(Iterable iterable) { - this(iterable, null); - } - @Override public void subscribe(CoreSubscriber actual) { boolean knownToBeFinite; - Iterator it; + Spliterator sp; try { - knownToBeFinite = FluxIterable.checkFinite(iterable); - it = iterable.iterator(); + sp = this.iterable.spliterator(); + knownToBeFinite = FluxIterable.checkFinite(sp); } catch (Throwable e) { Operators.error(actual, Operators.onOperatorError(e, actual.currentContext())); return; } - subscribe(actual, it, knownToBeFinite, onClose); + subscribe(actual, sp, knownToBeFinite, onClose); } @Override @@ -100,37 +96,37 @@ } /** - * Common method to take an {@link Iterator} as a source of values. + * Common method to take an {@link Spliterator} as a source of values. * * @param s the subscriber to feed this iterator to - * @param it the {@link Iterator} to use as a predictable source of values + * @param sp the {@link Spliterator} to use as a predictable source of values */ - static void subscribe(CoreSubscriber s, Iterator it, boolean knownToBeFinite) { - subscribe(s, it, knownToBeFinite, null); + static void subscribe(CoreSubscriber s, Spliterator sp, boolean knownToBeFinite) { + subscribe(s, sp, knownToBeFinite, null); } /** - * Common method to take an {@link Iterator} as a source of values. + * Common method to take an {@link Spliterator} as a source of values. * * @param s the subscriber to feed this iterator to - * @param it the {@link Iterator} to use as a source of values + * @param sp the {@link Spliterator} to use as a source of values * @param onClose close handler to call once we're done with the iterator (provided it * is not null, this includes when the iteration errors or complete or the subscriber * is cancelled). Null to ignore. */ @SuppressWarnings("unchecked") - static void subscribe(CoreSubscriber s, Iterator it, + static void subscribe(CoreSubscriber s, Spliterator sp, boolean knownToBeFinite, @Nullable Runnable onClose) { //noinspection ConstantConditions - if (it == null) { + if (sp == null) { Operators.error(s, new NullPointerException("The iterator is null")); return; } - boolean b; + boolean isEmpty; try { - b = it.hasNext(); + isEmpty = knownToBeFinite && sp.estimateSize() == 0; } catch (Throwable e) { Operators.error(s, Operators.onOperatorError(e, s.currentContext())); @@ -144,7 +140,7 @@ } return; } - if (!b) { + if (isEmpty) { Operators.complete(s); if (onClose != null) { try { @@ -158,22 +154,62 @@ } if (s instanceof ConditionalSubscriber) { - s.onSubscribe(new IterableSubscriptionConditional<>((ConditionalSubscriber) s, - it, knownToBeFinite, onClose)); + IterableSubscriptionConditional isc = + new IterableSubscriptionConditional<>((ConditionalSubscriber) s, + sp, + knownToBeFinite, + onClose); + + boolean hasNext; + try { + hasNext = isc.hasNext(); + } + catch (Throwable ex) { + Operators.error(s, ex); + isc.onCloseWithDropError(); + return; + } + + if (!hasNext) { + Operators.complete(s); + isc.onCloseWithDropError(); + return; + } + + s.onSubscribe(isc); } else { - s.onSubscribe(new IterableSubscription<>(s, it, knownToBeFinite, onClose)); + IterableSubscription is = + new IterableSubscription<>(s, sp, knownToBeFinite, onClose); + + boolean hasNext; + try { + hasNext = is.hasNext(); + } + catch (Throwable ex) { + Operators.error(s, ex); + is.onCloseWithDropError(); + return; + } + + if (!hasNext) { + Operators.complete(s); + is.onCloseWithDropError(); + return; + } + + s.onSubscribe(is); } } static final class IterableSubscription - implements InnerProducer, SynchronousSubscription { + implements InnerProducer, SynchronousSubscription, Consumer { final CoreSubscriber actual; - final Iterator iterator; - final boolean knownToBeFinite; - final Runnable onClose; + final Spliterator spliterator; + final boolean knownToBeFinite; + final Runnable onClose; volatile boolean cancelled; @@ -204,22 +240,50 @@ static final int STATE_CALL_HAS_NEXT = 3; T current; + + boolean valueReady = false; + + T nextElement; + Throwable hasNextFailure; IterableSubscription(CoreSubscriber actual, - Iterator iterator, boolean knownToBeFinite, @Nullable Runnable onClose) { + Spliterator spliterator, boolean knownToBeFinite, @Nullable Runnable onClose) { this.actual = actual; - this.iterator = iterator; + this.spliterator = spliterator; this.knownToBeFinite = knownToBeFinite; this.onClose = onClose; } IterableSubscription(CoreSubscriber actual, - Iterator iterator, boolean knownToBeFinite) { - this(actual, iterator, knownToBeFinite, null); + Spliterator spliterator, boolean knownToBeFinite) { + this(actual, spliterator, knownToBeFinite, null); } @Override + public void accept(T t) { + valueReady = true; + nextElement = t; + } + + boolean hasNext() { + if (!valueReady) + spliterator.tryAdvance(this); + return valueReady; + } + + T next() { + if (!valueReady && !hasNext()) + throw new NoSuchElementException(); + else { + valueReady = false; + T t = nextElement; + nextElement = null; + return t; + } + } + + @Override public void request(long n) { if (Operators.validate(n)) { if (Operators.addCap(REQUESTED, this, n) == 0) { @@ -245,7 +309,6 @@ } void slowPath(long n) { - final Iterator a = iterator; final Subscriber s = actual; long e = 0L; @@ -256,7 +319,7 @@ T t; try { - t = Objects.requireNonNull(a.next(), + t = Objects.requireNonNull(next(), "The iterator returned a null value"); } catch (Throwable ex) { @@ -278,7 +341,7 @@ boolean b; try { - b = a.hasNext(); + b = hasNext(); } catch (Throwable ex) { s.onError(ex); @@ -312,7 +375,6 @@ } void fastPath() { - final Iterator a = iterator; final Subscriber s = actual; for (; ; ) { @@ -324,7 +386,7 @@ T t; try { - t = Objects.requireNonNull(a.next(), + t = Objects.requireNonNull(next(), "The iterator returned a null value"); } catch (Exception ex) { @@ -346,7 +408,7 @@ boolean b; try { - b = a.hasNext(); + b = hasNext(); } catch (Exception ex) { s.onError(ex); @@ -370,7 +432,8 @@ public void cancel() { onCloseWithDropError(); cancelled = true; - Operators.onDiscardMultiple(this.iterator, this.knownToBeFinite, actual.currentContext()); + Operators.onDiscard(nextElement, actual.currentContext()); + Operators.onDiscardMultiple(this.spliterator, this.knownToBeFinite, actual.currentContext()); } @Override @@ -391,7 +454,8 @@ @Override public void clear() { - Operators.onDiscardMultiple(this.iterator, this.knownToBeFinite, actual.currentContext()); + Operators.onDiscard(nextElement, actual.currentContext()); + Operators.onDiscardMultiple(this.spliterator, this.knownToBeFinite, actual.currentContext()); state = STATE_NO_NEXT; } @@ -410,7 +474,7 @@ else { boolean hasNext; try { - hasNext = iterator.hasNext(); + hasNext = hasNext(); } catch (Throwable t) { //this is a corner case, most Iterators are not expected to throw in hasNext. @@ -441,7 +505,7 @@ if (!isEmpty()) { T c; if (state == STATE_HAS_NEXT_NO_VALUE) { - c = iterator.next(); + c = next(); } else { c = current; @@ -468,11 +532,11 @@ } static final class IterableSubscriptionConditional - implements InnerProducer, SynchronousSubscription { + implements InnerProducer, SynchronousSubscription, Consumer { final ConditionalSubscriber actual; - final Iterator iterator; + final Spliterator spliterator; final boolean knownToBeFinite; final Runnable onClose; @@ -506,22 +570,49 @@ T current; + boolean valueReady = false; + + T nextElement; + Throwable hasNextFailure; IterableSubscriptionConditional(ConditionalSubscriber actual, - Iterator iterator, boolean knownToBeFinite, @Nullable Runnable onClose) { + Spliterator spliterator, boolean knownToBeFinite, @Nullable Runnable onClose) { this.actual = actual; - this.iterator = iterator; + this.spliterator = spliterator; this.knownToBeFinite = knownToBeFinite; this.onClose = onClose; } IterableSubscriptionConditional(ConditionalSubscriber actual, - Iterator iterator, boolean knownToBeFinite) { - this(actual, iterator, knownToBeFinite, null); + Spliterator spliterator, boolean knownToBeFinite) { + this(actual, spliterator, knownToBeFinite, null); } @Override + public void accept(T t) { + valueReady = true; + nextElement = t; + } + + boolean hasNext() { + if (!valueReady) + spliterator.tryAdvance(this); + return valueReady; + } + + T next() { + if (!valueReady && !hasNext()) + throw new NoSuchElementException(); + else { + valueReady = false; + T t = nextElement; + nextElement = null; + return t; + } + } + + @Override public void request(long n) { if (Operators.validate(n)) { if (Operators.addCap(REQUESTED, this, n) == 0) { @@ -547,7 +638,6 @@ } void slowPath(long n) { - final Iterator a = iterator; final ConditionalSubscriber s = actual; long e = 0L; @@ -558,7 +648,7 @@ T t; try { - t = Objects.requireNonNull(a.next(), + t = Objects.requireNonNull(next(), "The iterator returned a null value"); } catch (Throwable ex) { @@ -580,7 +670,7 @@ boolean b; try { - b = a.hasNext(); + b = hasNext(); } catch (Throwable ex) { s.onError(ex); @@ -616,7 +706,6 @@ } void fastPath() { - final Iterator a = iterator; final ConditionalSubscriber s = actual; for (; ; ) { @@ -628,7 +717,7 @@ T t; try { - t = Objects.requireNonNull(a.next(), + t = Objects.requireNonNull(next(), "The iterator returned a null value"); } catch (Exception ex) { @@ -650,7 +739,7 @@ boolean b; try { - b = a.hasNext(); + b = hasNext(); } catch (Exception ex) { s.onError(ex); @@ -674,7 +763,8 @@ public void cancel() { onCloseWithDropError(); cancelled = true; - Operators.onDiscardMultiple(this.iterator, this.knownToBeFinite, actual.currentContext()); + Operators.onDiscard(this.nextElement, actual.currentContext()); + Operators.onDiscardMultiple(this.spliterator, this.knownToBeFinite, actual.currentContext()); } @Override @@ -695,7 +785,8 @@ @Override public void clear() { - Operators.onDiscardMultiple(this.iterator, this.knownToBeFinite, actual.currentContext()); + Operators.onDiscard(this.nextElement, actual.currentContext()); + Operators.onDiscardMultiple(this.spliterator, this.knownToBeFinite, actual.currentContext()); state = STATE_NO_NEXT; } @@ -714,7 +805,7 @@ else { boolean hasNext; try { - hasNext = iterator.hasNext(); + hasNext = hasNext(); } catch (Throwable t) { //this is a corner case, most Iterators are not expected to throw in hasNext. @@ -745,7 +836,7 @@ if (!isEmpty()) { T c; if (state == STATE_HAS_NEXT_NO_VALUE) { - c = iterator.next(); + c = next(); } else { c = current; Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxMap.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxMap.java (.../FluxMap.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxMap.java (.../FluxMap.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -103,8 +103,10 @@ R v; try { - v = Objects.requireNonNull(mapper.apply(t), - "The mapper returned a null value."); + v = mapper.apply(t); + if (v == null) { + throw new NullPointerException("The mapper [" + mapper.getClass().getName() + "] returned a null value."); + } } catch (Throwable e) { Throwable e_ = Operators.onNextError(t, e, actual.currentContext(), s); @@ -203,8 +205,10 @@ R v; try { - v = Objects.requireNonNull(mapper.apply(t), - "The mapper returned a null value."); + v = mapper.apply(t); + if (v == null) { + throw new NullPointerException("The mapper [" + mapper.getClass().getName() + "] returned a null value."); + } } catch (Throwable e) { Throwable e_ = Operators.onNextError(t, e, actual.currentContext(), s); @@ -230,8 +234,10 @@ R v; try { - v = Objects.requireNonNull(mapper.apply(t), - "The mapper returned a null value."); + v = mapper.apply(t); + if (v == null) { + throw new NullPointerException("The mapper [" + mapper.getClass().getName() + "] returned a null value."); + } return actual.tryOnNext(v); } catch (Throwable e) { Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxMapFuseable.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxMapFuseable.java (.../FluxMapFuseable.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxMapFuseable.java (.../FluxMapFuseable.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -110,8 +110,10 @@ R v; try { - v = Objects.requireNonNull(mapper.apply(t), - "The mapper returned a null value."); + v = mapper.apply(t); + if (v == null) { + throw new NullPointerException("The mapper [" + mapper.getClass().getName() + "] returned a null value."); + } } catch (Throwable e) { Throwable e_ = Operators.onNextError(t, e, actual.currentContext(), s); @@ -278,8 +280,10 @@ R v; try { - v = Objects.requireNonNull(mapper.apply(t), - "The mapper returned a null value."); + v = mapper.apply(t); + if (v == null) { + throw new NullPointerException("The mapper [" + mapper.getClass().getName() + "] returned a null value."); + } } catch (Throwable e) { Throwable e_ = Operators.onNextError(t, e, actual.currentContext(), s); @@ -306,8 +310,10 @@ R v; try { - v = Objects.requireNonNull(mapper.apply(t), - "The mapper returned a null value."); + v = mapper.apply(t); + if (v == null) { + throw new NullPointerException("The mapper [" + mapper.getClass().getName() + "] returned a null value."); + } return actual.tryOnNext(v); } catch (Throwable e) { Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxMapSignal.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxMapSignal.java (.../FluxMapSignal.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxMapSignal.java (.../FluxMapSignal.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -141,8 +141,10 @@ R v; try { - v = Objects.requireNonNull(mapperNext.apply(t), - "The mapper returned a null value."); + v = mapperNext.apply(t); + if (v == null) { + throw new NullPointerException("The mapper [" + mapperNext.getClass().getName() + "] returned a null value."); + } } catch (Throwable e) { done = true; @@ -171,8 +173,10 @@ R v; try { - v = Objects.requireNonNull(mapperError.apply(t), - "The mapper returned a null value."); + v = mapperError.apply(t); + if (v == null) { + throw new NullPointerException("The mapper [" + mapperError.getClass().getName() + "] returned a null value."); + } } catch (Throwable e) { done = true; @@ -203,8 +207,10 @@ R v; try { - v = Objects.requireNonNull(mapperComplete.get(), - "The mapper returned a null value."); + v = mapperComplete.get(); + if (v == null) { + throw new NullPointerException("The mapper [" + mapperComplete.getClass().getName() + "] returned a null value."); + } } catch (Throwable e) { done = true; Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxMergeComparing.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxMergeComparing.java (.../FluxMergeComparing.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxMergeComparing.java (.../FluxMergeComparing.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2018-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,7 +37,7 @@ * Merges the provided sources {@link org.reactivestreams.Publisher}, assuming a total order * of the values, by picking the smallest available value from each publisher, resulting in * a single totally ordered {@link Flux} sequence. This operator considers its primary - * parent to be the first of the sources, for the purpose of {@link reactor.core.Scannable.Attr#PARENT}. + * parent to be the first of the sources, for the purpose of {@link Attr#PARENT}. * * @param the value type * @author David Karnok @@ -50,9 +50,11 @@ final Comparator valueComparator; final Publisher[] sources; final boolean delayError; + final boolean waitForAllSources; @SafeVarargs - FluxMergeComparing(int prefetch, Comparator valueComparator, boolean delayError, Publisher... sources) { + FluxMergeComparing(int prefetch, Comparator valueComparator, boolean delayError, + boolean waitForAllSources, Publisher... sources) { if (prefetch <= 0) { throw new IllegalArgumentException("prefetch > 0 required but it was " + prefetch); } @@ -68,6 +70,7 @@ this.prefetch = prefetch; this.valueComparator = valueComparator; this.delayError = delayError; + this.waitForAllSources = waitForAllSources; } /** @@ -91,9 +94,9 @@ @SuppressWarnings("unchecked") Comparator currentComparator = (Comparator) this.valueComparator; final Comparator newComparator = currentComparator.thenComparing(otherComparator); - return new FluxMergeComparing<>(prefetch, newComparator, delayError, newArray); + return new FluxMergeComparing<>(prefetch, newComparator, delayError, waitForAllSources, newArray); } - return new FluxMergeComparing<>(prefetch, valueComparator, delayError, newArray); + return new FluxMergeComparing<>(prefetch, valueComparator, delayError, waitForAllSources, newArray); } @Override @@ -114,7 +117,7 @@ @Override public void subscribe(CoreSubscriber actual) { - MergeOrderedMainProducer main = new MergeOrderedMainProducer<>(actual, valueComparator, prefetch, sources.length, delayError); + MergeOrderedMainProducer main = new MergeOrderedMainProducer<>(actual, valueComparator, prefetch, sources.length, delayError, waitForAllSources); actual.onSubscribe(main); main.subscribe(sources); } @@ -129,6 +132,7 @@ final Comparator comparator; final Object[] values; final boolean delayError; + final boolean waitForAllSources; boolean done; @@ -154,10 +158,11 @@ AtomicIntegerFieldUpdater.newUpdater(MergeOrderedMainProducer.class, "wip"); MergeOrderedMainProducer(CoreSubscriber actual, - Comparator comparator, int prefetch, int n, boolean delayError) { + Comparator comparator, int prefetch, int n, boolean delayError, boolean waitForAllSources) { this.actual = actual; this.comparator = comparator; this.delayError = delayError; + this.waitForAllSources = waitForAllSources; @SuppressWarnings("unchecked") MergeOrderedInnerSubscriber[] mergeOrderedInnerSub = @@ -285,7 +290,7 @@ return; } - if (nonEmpty != n || e >= r) { + if ((waitForAllSources && nonEmpty != n) || (!waitForAllSources && (nonEmpty == 0 || nonEmpty == innerDoneCount)) || e >= r) { break; } @@ -294,7 +299,7 @@ int i = 0; for (Object o : values) { - if (o != DONE) { + if (o != DONE && o != null) { boolean smaller; try { @SuppressWarnings("unchecked") @@ -316,12 +321,14 @@ i++; } - values[minIndex] = null; + if (minIndex >= 0) { + values[minIndex] = null; - actual.onNext(min); + actual.onNext(min); - e++; - subscribers[minIndex].request(1); + e++; + subscribers[minIndex].request(1); + } } this.emitted = e; @@ -488,4 +495,4 @@ return null; } } -} +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxMergeSequential.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxMergeSequential.java (.../FluxMergeSequential.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxMergeSequential.java (.../FluxMergeSequential.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -311,15 +311,22 @@ } void innerError(MergeSequentialInner inner, Throwable e) { - if (Exceptions.addThrowable(ERROR, this, e)) { - inner.setDone(); - if (errorMode != ErrorMode.END) { - s.cancel(); + e = Operators.onNextInnerError(e, currentContext(), s); + if (e != null) { + if (Exceptions.addThrowable(ERROR, this, e)) { + inner.setDone(); + if (errorMode != ErrorMode.END) { + s.cancel(); + } + drain(); } - drain(); + else { + Operators.onErrorDropped(e, actual.currentContext()); + } } else { - Operators.onErrorDropped(e, actual.currentContext()); + inner.setDone(); + drain(); } } Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxMetrics.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxMetrics.java (.../FluxMetrics.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxMetrics.java (.../FluxMetrics.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2018-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,10 +16,9 @@ package reactor.core.publisher; -import java.util.LinkedList; +import java.util.List; import java.util.concurrent.TimeUnit; -import java.util.function.BiFunction; -import java.util.function.BinaryOperator; +import java.util.stream.Collectors; import io.micrometer.core.instrument.Clock; import io.micrometer.core.instrument.Counter; @@ -30,12 +29,12 @@ import io.micrometer.core.instrument.Timer; import org.reactivestreams.Publisher; import org.reactivestreams.Subscription; + import reactor.core.CoreSubscriber; import reactor.core.Scannable; import reactor.util.Logger; import reactor.util.Loggers; import reactor.util.Metrics.MicrometerConfiguration; -import reactor.util.function.Tuple2; /** * Activate metrics gathering on a {@link Flux}, assuming Micrometer is on the classpath. @@ -46,6 +45,7 @@ * @author Simon Baslé * @author Stephane Maldini */ +@Deprecated final class FluxMetrics extends InternalFluxOperator { final String name; @@ -257,10 +257,6 @@ static final Logger log = Loggers.getLogger(FluxMetrics.class); - static final BiFunction, Tags> TAG_ACCUMULATOR = - (prev, tuple) -> prev.and(Tag.of(tuple.getT1(), tuple.getT2())); - static final BinaryOperator TAG_COMBINER = Tags::and; - /** * Extract the name from the upstream, and detect if there was an actual name (ie. distinct from {@link * Scannable#stepName()}) set by the user. @@ -299,13 +295,11 @@ Scannable scannable = Scannable.from(source); if (scannable.isScanAvailable()) { - LinkedList> scannableTags = new LinkedList<>(); - scannable.tags().forEach(scannableTags::push); - return scannableTags.stream() - //Note the combiner below is for parallel streams, which won't be used - //For the identity, `commonTags` should be ok (even if reduce uses it multiple times) - //since it deduplicates - .reduce(tags, TAG_ACCUMULATOR, TAG_COMBINER); + List discoveredTags = scannable.tagsDeduplicated() + .entrySet().stream() + .map(e -> Tag.of(e.getKey(), e.getValue())) + .collect(Collectors.toList()); + return tags.and(discoveredTags); } return tags; Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxMetricsFuseable.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxMetricsFuseable.java (.../FluxMetricsFuseable.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxMetricsFuseable.java (.../FluxMetricsFuseable.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2018-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,6 +39,7 @@ * @author Simon Baslé * @author Stephane Maldini */ +@Deprecated final class FluxMetricsFuseable extends InternalFluxOperator implements Fuseable { final String name; @@ -71,13 +72,13 @@ * subsequences immediately upstream of it, so Counters would be a bit irrelevant. We however want to instrument * onNext counts. */ - static final class MetricsFuseableSubscriber extends FluxMetrics.MetricsSubscriber + static final class MetricsFuseableSubscriber extends MetricsSubscriber implements Fuseable, QueueSubscription { int mode; @Nullable - Fuseable.QueueSubscription qs; + QueueSubscription qs; MetricsFuseableSubscriber(CoreSubscriber actual, MeterRegistry registry, @@ -178,4 +179,4 @@ return qs == null ? 0 : qs.size(); } } -} +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxName.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxName.java (.../FluxName.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxName.java (.../FluxName.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,9 +17,9 @@ package reactor.core.publisher; import java.util.Collections; -import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; import java.util.Objects; -import java.util.Set; import reactor.core.CoreSubscriber; import reactor.core.Fuseable; @@ -32,7 +32,7 @@ /** * An operator that just bears a name or a set of tags, which can be retrieved via the - * {@link reactor.core.Scannable.Attr#TAGS TAGS} + * {@link Attr#TAGS TAGS} * attribute. * * @author Simon Baslé @@ -42,61 +42,68 @@ final String name; - final Set> tags; + final List> tagsWithDuplicates; - @SuppressWarnings("unchecked") static Flux createOrAppend(Flux source, String name) { Objects.requireNonNull(name, "name"); if (source instanceof FluxName) { FluxName s = (FluxName) source; - return new FluxName<>(s.source, name, s.tags); + return new FluxName<>(s.source, name, s.tagsWithDuplicates); } if (source instanceof FluxNameFuseable) { FluxNameFuseable s = (FluxNameFuseable) source; - return new FluxNameFuseable<>(s.source, name, s.tags); + return new FluxNameFuseable<>(s.source, name, s.tagsWithDuplicates); } if (source instanceof Fuseable) { return new FluxNameFuseable<>(source, name, null); } return new FluxName<>(source, name, null); } - @SuppressWarnings("unchecked") static Flux createOrAppend(Flux source, String tagName, String tagValue) { Objects.requireNonNull(tagName, "tagName"); Objects.requireNonNull(tagValue, "tagValue"); - Set> tags = Collections.singleton(Tuples.of(tagName, tagValue)); + Tuple2 newTag = Tuples.of(tagName, tagValue); if (source instanceof FluxName) { FluxName s = (FluxName) source; - if(s.tags != null) { - tags = new HashSet<>(tags); - tags.addAll(s.tags); + List> tags; + if(s.tagsWithDuplicates != null) { + tags = new LinkedList<>(s.tagsWithDuplicates); + tags.add(newTag); } + else { + tags = Collections.singletonList(newTag); + } return new FluxName<>(s.source, s.name, tags); } + if (source instanceof FluxNameFuseable) { FluxNameFuseable s = (FluxNameFuseable) source; - if (s.tags != null) { - tags = new HashSet<>(tags); - tags.addAll(s.tags); + List> tags; + if (s.tagsWithDuplicates != null) { + tags = new LinkedList<>(s.tagsWithDuplicates); + tags.add(newTag); } + else { + tags = Collections.singletonList(newTag); + } return new FluxNameFuseable<>(s.source, s.name, tags); } if (source instanceof Fuseable) { - return new FluxNameFuseable<>(source, null, tags); + return new FluxNameFuseable<>(source, null, Collections.singletonList(newTag)); } - return new FluxName<>(source, null, tags); + return new FluxName<>(source, null, Collections.singletonList(newTag)); } FluxName(Flux source, @Nullable String name, - @Nullable Set> tags) { + @Nullable List> tags) { super(source); this.name = name; - this.tags = tags; + this.tagsWithDuplicates = tags; } @Override @@ -111,8 +118,8 @@ return name; } - if (key == Attr.TAGS && tags != null) { - return tags.stream(); + if (key == Attr.TAGS && tagsWithDuplicates != null) { + return tagsWithDuplicates.stream(); } if (key == RUN_STYLE) { @@ -121,6 +128,4 @@ return super.scanUnsafe(key); } - - -} +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxNameFuseable.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxNameFuseable.java (.../FluxNameFuseable.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxNameFuseable.java (.../FluxNameFuseable.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,15 +16,14 @@ package reactor.core.publisher; -import java.util.Set; +import java.util.List; import reactor.core.CoreSubscriber; import reactor.core.Fuseable; import reactor.util.annotation.Nullable; import reactor.util.function.Tuple2; import static reactor.core.Scannable.Attr.RUN_STYLE; -import static reactor.core.Scannable.Attr.RunStyle.SYNC; /** * An operator that just bears a name or a set of tags, which can be retrieved via the @@ -38,14 +37,14 @@ final String name; - final Set> tags; + final List> tagsWithDuplicates; FluxNameFuseable(Flux source, @Nullable String name, - @Nullable Set> tags) { + @Nullable List> tags) { super(source); this.name = name; - this.tags = tags; + this.tagsWithDuplicates = tags; } @Override @@ -60,8 +59,8 @@ return name; } - if (key == Attr.TAGS && tags != null) { - return tags.stream(); + if (key == Attr.TAGS && tagsWithDuplicates != null) { + return tagsWithDuplicates.stream(); } if (key == RUN_STYLE) { Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxOnErrorReturn.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxOnErrorReturn.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxOnErrorReturn.java (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2022 VMware Inc. or its affiliates, All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reactor.core.publisher; + +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.function.Predicate; + +import org.reactivestreams.Subscription; + +import reactor.core.CoreSubscriber; +import reactor.util.annotation.Nullable; + +/** + * In case of onError, complete the sequence with a fallback value or simply with the onComplete signal in case said value is null. + * Only consider {@link Throwable} that match the provided {@link Predicate}, if any. + *

      + * This operator is behind both {@link Flux#onErrorReturn(Object)} and {@link Flux#onErrorComplete()} APIs. + * + * @param the value type + */ +final class FluxOnErrorReturn extends InternalFluxOperator { + + @Nullable + final Predicate resumableErrorPredicate; + + @Nullable + final T fallbackValue; + + FluxOnErrorReturn(Flux source, @Nullable Predicate predicate, @Nullable T value) { + super(source); + resumableErrorPredicate = predicate; + fallbackValue = value; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new ReturnSubscriber<>(actual, resumableErrorPredicate, fallbackValue, false); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class ReturnSubscriber implements InnerOperator { + + private static final byte STATE_CANCELLED = -3; + private static final byte STATE_TERMINATED = -2; + private static final byte STATE_PENDING_FALLBACK = -1; + + final CoreSubscriber actual; + final boolean trackRequestWhenFallbackDeferred; + + Subscription s; + + @Nullable + final Predicate resumableErrorPredicate; + + @Nullable + final T fallbackValue; + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(ReturnSubscriber.class, "requested"); + + + ReturnSubscriber(CoreSubscriber actual, @Nullable Predicate predicate, + @Nullable T value, boolean trackRequestWhenFallbackDeferred) { + this.actual = actual; + resumableErrorPredicate = predicate; + fallbackValue = value; + this.trackRequestWhenFallbackDeferred = trackRequestWhenFallbackDeferred; + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + actual.onSubscribe(this); + } + } + + @Override + public CoreSubscriber actual() { + return this.actual; + } + + void producedOne() { + long r; + for (;;) { + r = REQUESTED.get(this); + if (r == Long.MAX_VALUE || r <= 0) { + return; + } + + if (REQUESTED.compareAndSet(this, r, r - 1)) { + return; + } + } + } + + @Override + public void cancel() { + if (REQUESTED.getAndSet(this, STATE_CANCELLED) != STATE_CANCELLED) { + s.cancel(); + } + } + + @Override + public void request(long n) { + long r; + for (;;) { + r = REQUESTED.get(this); + if (r == Long.MAX_VALUE || r < STATE_PENDING_FALLBACK) { + return; + } + + if (r == STATE_PENDING_FALLBACK) { + //ensure no concurrent request took care of it already + if (REQUESTED.compareAndSet(this, r, STATE_TERMINATED)) { + if (this.fallbackValue != null) { //should be always true at this point + actual.onNext(this.fallbackValue); + } + actual.onComplete(); + //optionally still transmit the request to upstream + cancel it + // to indicate eg. stress tests that we went down this code path + if (this.trackRequestWhenFallbackDeferred) { + s.request(n); + s.cancel(); + } + } + return; + } + + long u = Operators.addCap(r, n); + if (REQUESTED.compareAndSet(this, r, u)) { + s.request(n); + return; + } + } + } + + @Override + public void onNext(T t) { + producedOne(); + actual.onNext(t); + } + + @Override + public void onComplete() { + if (this.requested != STATE_CANCELLED) { + REQUESTED.set(this, STATE_TERMINATED); + actual.onComplete(); + } + } + + @Override + public void onError(Throwable t) { + boolean shouldResume = this.resumableErrorPredicate == null || this.resumableErrorPredicate.test(t); + if (!shouldResume) { + //it is always fine to propagate errors even when there is no pending request + if (this.requested != STATE_CANCELLED) { + REQUESTED.set(this, STATE_TERMINATED); + actual.onError(t); + } + return; + } + + if (this.fallbackValue == null) { + //it is always fine to send onComplete() even when there is no pending request + if (this.requested != STATE_CANCELLED) { + REQUESTED.set(this, STATE_TERMINATED); + actual.onComplete(); + } + return; + } + +// in case of a fallback value, we need to make sure we don't send onNext(fallback) if there is no request + long r = this.requested; + if (r > 0) { + //upstream won't produce anymore so we're confident the fallback value won't compete with an onNext for demand + REQUESTED.set(this, STATE_TERMINATED); + actual.onNext(this.fallbackValue); + actual.onComplete(); + return; + } + + //now the interesting case: r == 0 means we MUST wait for the next request before delivering the fallback value + //upstream still won't produce, so the requested won't further diminish via producedOne() + //we try to turn that 0 into a marker for pending fallback but if we fail we might want to propagate fallback ourselves + if (!REQUESTED.compareAndSet(this, 0, STATE_PENDING_FALLBACK)) { + //re-check the request: + r = this.requested; + if (r > 0) { + REQUESTED.set(this, STATE_TERMINATED); + actual.onNext(this.fallbackValue); + actual.onComplete(); + } + //at this point we failed to swap to PENDING_FALLBACK but there is no measurable request, which MUST mean we were cancelled + } // else next request will take care of delivering + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.PARENT) return s; + + long r = this.requested; + if (key == Attr.CANCELLED) return r == STATE_CANCELLED; + if (key == Attr.TERMINATED) return r == STATE_TERMINATED; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return r > 0 ? r : 0; + + return InnerOperator.super.scanUnsafe(key); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxProcessor.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxProcessor.java (.../FluxProcessor.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxProcessor.java (.../FluxProcessor.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,8 +41,8 @@ * * @param the input value type * @param the output value type - * @deprecated Processors will be removed in 3.5. Prefer using {@link Sinks.Many} instead, - * * or see https://github.com/reactor/reactor-core/issues/2431 for alternatives + * @deprecated Processors will be removed in 3.5. Prefer using {@link Many} instead, + * or see https://github.com/reactor/reactor-core/issues/2431 for alternatives */ @Deprecated public abstract class FluxProcessor extends Flux Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxPublish.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxPublish.java (.../FluxPublish.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxPublish.java (.../FluxPublish.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,6 @@ import java.util.Objects; import java.util.Queue; import java.util.concurrent.CancellationException; -import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.concurrent.atomic.AtomicLongFieldUpdater; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import java.util.function.Consumer; @@ -57,6 +56,11 @@ final Supplier> queueSupplier; + /** + * Whether to prepare for a reconnect after the source terminates. + */ + final boolean resetUponSourceTermination; + volatile PublishSubscriber connection; @SuppressWarnings("rawtypes") static final AtomicReferenceFieldUpdater CONNECTION = @@ -66,13 +70,15 @@ FluxPublish(Flux source, int prefetch, - Supplier> queueSupplier) { + Supplier> queueSupplier, + boolean resetUponSourceTermination) { if (prefetch <= 0) { throw new IllegalArgumentException("bufferSize > 0 required but it was " + prefetch); } this.source = Objects.requireNonNull(source, "source"); this.prefetch = prefetch; this.queueSupplier = Objects.requireNonNull(queueSupplier, "queueSupplier"); + this.resetUponSourceTermination = resetUponSourceTermination; } @Override @@ -91,7 +97,7 @@ s = u; } - doConnect = s.tryConnect(); + doConnect = s.tryConnect(); break; } @@ -111,7 +117,7 @@ } PublishSubscriber c = connection; - if (c == null || c.isTerminated()) { + if (c == null || (this.resetUponSourceTermination && c.isTerminated())) { PublishSubscriber u = new PublishSubscriber<>(prefetch, this); if (!CONNECTION.compareAndSet(this, c, u)) { continue; @@ -127,9 +133,18 @@ else { inner.parent = c; } - c.drain(); + + c.drainFromInner(); break; } + else if (!this.resetUponSourceTermination) { + if (c.error != null) { + inner.actual.onError(c.error); + } else { + inner.actual.onComplete(); + } + break; + } } } @@ -155,12 +170,7 @@ final FluxPublish parent; - volatile Subscription s; - @SuppressWarnings("rawtypes") - static final AtomicReferenceFieldUpdater S = - AtomicReferenceFieldUpdater.newUpdater(PublishSubscriber.class, - Subscription.class, - "s"); + Subscription s; volatile PubSubInner[] subscribers; @@ -170,17 +180,11 @@ PubSubInner[].class, "subscribers"); - volatile int wip; + volatile long state; @SuppressWarnings("rawtypes") - static final AtomicIntegerFieldUpdater WIP = - AtomicIntegerFieldUpdater.newUpdater(PublishSubscriber.class, "wip"); + static final AtomicLongFieldUpdater STATE = + AtomicLongFieldUpdater.newUpdater(PublishSubscriber.class, "state"); - volatile int connected; - @SuppressWarnings("rawtypes") - static final AtomicIntegerFieldUpdater CONNECTED = - AtomicIntegerFieldUpdater.newUpdater(PublishSubscriber.class, - "connected"); - //notes: FluxPublish needs to distinguish INIT from CANCELLED in order to correctly //drop values in case of an early connect() without any subscribers. @SuppressWarnings("rawtypes") @@ -190,11 +194,11 @@ @SuppressWarnings("rawtypes") static final PubSubInner[] TERMINATED = new PublishInner[0]; - volatile Queue queue; + Queue queue; int sourceMode; - volatile boolean done; + boolean done; volatile Throwable error; @@ -204,7 +208,6 @@ Throwable.class, "error"); - @SuppressWarnings("unchecked") PublishSubscriber(int prefetch, FluxPublish parent) { this.prefetch = prefetch; this.parent = parent; @@ -217,7 +220,9 @@ @Override public void onSubscribe(Subscription s) { - if (Operators.setOnce(S, this, s)) { + if (Operators.validate(this.s, s)) { + this.s = s; + if (s instanceof Fuseable.QueueSubscription) { @SuppressWarnings("unchecked") Fuseable.QueueSubscription f = (Fuseable.QueueSubscription) s; @@ -226,46 +231,82 @@ if (m == Fuseable.SYNC) { sourceMode = m; queue = f; - drain(); + long previousState = markSubscriptionSetAndAddWork(this); + if (isCancelled(previousState)) { + s.cancel(); + return; + } + + if (hasWorkInProgress(previousState)) { + return; + } + + drain(previousState | SUBSCRIPTION_SET_FLAG | 1); return; } + if (m == Fuseable.ASYNC) { sourceMode = m; queue = f; - s.request(Operators.unboundedOrPrefetch(prefetch)); + long previousState = markSubscriptionSet(this); + if (isCancelled(previousState)) { + s.cancel(); + } + else { + s.request(Operators.unboundedOrPrefetch(prefetch)); + } return; } } queue = parent.queueSupplier.get(); - - s.request(Operators.unboundedOrPrefetch(prefetch)); + long previousState = markSubscriptionSet(this); + if (isCancelled(previousState)) { + s.cancel(); + } + else { + s.request(Operators.unboundedOrPrefetch(prefetch)); + } } } @Override - public void onNext(T t) { + public void onNext(@Nullable T t) { if (done) { if (t != null) { Operators.onNextDropped(t, currentContext()); } return; } - if (sourceMode == Fuseable.ASYNC) { - drain(); - return; - } - - if (!queue.offer(t)) { + boolean isAsyncMode = sourceMode == Fuseable.ASYNC; + if (!isAsyncMode && !queue.offer(t)) { Throwable ex = Operators.onOperatorError(s, - Exceptions.failWithOverflow(Exceptions.BACKPRESSURE_ERROR_QUEUE_FULL), t, currentContext()); + Exceptions.failWithOverflow(Exceptions.BACKPRESSURE_ERROR_QUEUE_FULL), + t, + currentContext()); if (!Exceptions.addThrowable(ERROR, this, ex)) { Operators.onErrorDroppedMulticast(ex, subscribers); return; } done = true; } - drain(); + + long previousState = addWork(this); + + if (isFinalized(previousState)) { + clear(); + return; + } + + if (isTerminated(previousState) || isCancelled(previousState)) { + return; + } + + if (hasWorkInProgress(previousState)) { + return; + } + + drain(previousState + 1); } @Override @@ -274,13 +315,22 @@ Operators.onErrorDroppedMulticast(t, subscribers); return; } - if (Exceptions.addThrowable(ERROR, this, t)) { - done = true; - drain(); - } - else { + + done = true; + if (!Exceptions.addThrowable(ERROR, this, t)) { Operators.onErrorDroppedMulticast(t, subscribers); } + + long previousState = markTerminated(this); + if (isTerminated(previousState) || isCancelled(previousState)) { + return; + } + + if (hasWorkInProgress(previousState)) { + return; + } + + drain((previousState | TERMINATED_FLAG) + 1); } @Override @@ -289,7 +339,17 @@ return; } done = true; - drain(); + + long previousState = markTerminated(this); + if (isTerminated(previousState) || isCancelled(previousState)) { + return; + } + + if (hasWorkInProgress(previousState)) { + return; + } + + drain((previousState | TERMINATED_FLAG) + 1); } @Override @@ -298,19 +358,40 @@ return; } if (CONNECTION.compareAndSet(parent, this, null)) { - Operators.terminate(S, this); - if (WIP.getAndIncrement(this) != 0) { + long previousState = markCancelled(this); + if (isTerminated(previousState) || isCancelled(previousState)) { return; } - disconnectAction(); + + if (hasWorkInProgress(previousState)) { + return; + } + + disconnectAction(previousState); } } - void disconnectAction() { + void clear() { + if (sourceMode == Fuseable.NONE) { + T t; + while ((t = queue.poll()) != null) { + Operators.onDiscard(t, currentContext()); + } + } + else { + queue.clear(); + } + } + + void disconnectAction(long previousState) { + if (isSubscriptionSet(previousState)) { + this.s.cancel(); + clear(); + } + @SuppressWarnings("unchecked") PubSubInner[] inners = SUBSCRIBERS.getAndSet(this, CANCELLED); if (inners.length > 0) { - queue.clear(); CancellationException ex = new CancellationException("Disconnected"); for (PubSubInner inner : inners) { @@ -321,7 +402,7 @@ boolean add(PublishInner inner) { for (; ; ) { - FluxPublish.PubSubInner[] a = subscribers; + PubSubInner[] a = subscribers; if (a == TERMINATED) { return false; } @@ -335,7 +416,6 @@ } } - @SuppressWarnings("unchecked") public void remove(PubSubInner inner) { for (; ; ) { PubSubInner[] a = subscribers; @@ -379,25 +459,36 @@ } boolean tryConnect() { - return connected == 0 && CONNECTED.compareAndSet(this, 0, 1); + long previousState = markConnected(this); + + return !isConnected(previousState); } - final void drain() { - if (WIP.getAndIncrement(this) != 0) { + final void drainFromInner() { + long previousState = addWorkIfSubscribed(this); + + if (!isSubscriptionSet(previousState)) { return; } - int missed = 1; + if (hasWorkInProgress(previousState)) { + return; + } + drain(previousState + 1); + } + + final void drain(long expectedState) { for (; ; ) { boolean d = done; Queue q = queue; + int mode = sourceMode; boolean empty = q == null || q.isEmpty(); - if (checkTerminated(d, empty)) { + if (checkTerminated(d, empty, null)) { return; } @@ -432,10 +523,10 @@ d = true; v = null; } - if (checkTerminated(d, v == null)) { + if (checkTerminated(d, v == null, v)) { return; } - if (sourceMode != Fuseable.SYNC) { + if (mode != Fuseable.SYNC) { s.request(1); } continue; @@ -460,15 +551,15 @@ empty = v == null; - if (checkTerminated(d, empty)) { + if (checkTerminated(d, empty, v)) { return; } if (empty) { //async mode only needs to break but SYNC mode needs to perform terminal cleanup here... - if (sourceMode == Fuseable.SYNC) { + if (mode == Fuseable.SYNC) { done = true; - checkTerminated(true, true); + checkTerminated(true, true, null); } break; } @@ -485,46 +576,57 @@ e++; } - if (e != 0 && sourceMode != Fuseable.SYNC) { + if (e != 0 && mode != Fuseable.SYNC) { s.request(e); } if (maxRequested != 0L && !empty) { continue; } } - else if (sourceMode == Fuseable.SYNC) { + else if (q != null && mode == Fuseable.SYNC) { done = true; - if (checkTerminated(true, empty)) { + if (checkTerminated(true, empty, null)) { break; } } - missed = WIP.addAndGet(this, -missed); - if (missed == 0) { - break; + expectedState = markWorkDone(this, expectedState); + if (isCancelled(expectedState)) { + clearAndFinalize(this); + return; } + + if (!hasWorkInProgress(expectedState)) { + return; + } } } - boolean checkTerminated(boolean d, boolean empty) { - if (s == Operators.cancelledSubscription()) { - disconnectAction(); + boolean checkTerminated(boolean d, boolean empty, @Nullable T t) { + long state = this.state; + if (isCancelled(state)) { + Operators.onDiscard(t, currentContext()); + disconnectAction(state); return true; } if (d) { Throwable e = error; if (e != null && e != Exceptions.TERMINATED) { - CONNECTION.compareAndSet(parent, this, null); - e = Exceptions.terminate(ERROR, this); + if (parent.resetUponSourceTermination) { + CONNECTION.compareAndSet(parent, this, null); + e = Exceptions.terminate(ERROR, this); + } queue.clear(); for (PubSubInner inner : terminate()) { inner.actual.onError(e); } return true; } else if (empty) { - CONNECTION.compareAndSet(parent, this, null); + if (parent.resetUponSourceTermination) { + CONNECTION.compareAndSet(parent, this, null); + } for (PubSubInner inner : terminate()) { inner.actual.onComplete(); } @@ -560,9 +662,193 @@ @Override public boolean isDisposed() { - return s == Operators.cancelledSubscription() || done; + long state = this.state; + return isTerminated(state) || isCancelled(state); } + static void clearAndFinalize(PublishSubscriber instance) { + for (; ; ) { + final long state = instance.state; + + if (isFinalized(state)) { + instance.clear(); + return; + } + + if (isSubscriptionSet(state)) { + instance.clear(); + } + + if (STATE.compareAndSet( + instance, state, + (state & ~WORK_IN_PROGRESS_MASK) | FINALIZED_FLAG)) { + break; + } + } + } + + static long addWork(PublishSubscriber instance) { + for (;;) { + long state = instance.state; + + if (STATE.compareAndSet(instance, state, addWork(state))) { + return state; + } + } + } + + static long addWorkIfSubscribed(PublishSubscriber instance) { + for (;;) { + long state = instance.state; + + if (!isSubscriptionSet(state)) { + return state; + } + + if (STATE.compareAndSet(instance, state, addWork(state))) { + return state; + } + } + } + + static long addWork(long state) { + if ((state & WORK_IN_PROGRESS_MASK) == WORK_IN_PROGRESS_MASK) { + return (state &~ WORK_IN_PROGRESS_MASK) | 1; + } + else { + return state + 1; + } + } + + static long markTerminated(PublishSubscriber instance) { + for (;;) { + long state = instance.state; + + if (isCancelled(state) || isTerminated(state)) { + return state; + } + + long nextState = addWork(state); + if (STATE.compareAndSet(instance, state, nextState | TERMINATED_FLAG)) { + return state; + } + } + } + + static long markConnected(PublishSubscriber instance) { + for (;;) { + long state = instance.state; + + if (isConnected(state)) { + return state; + } + + if (STATE.compareAndSet(instance, state, state | CONNECTED_FLAG)) { + return state; + } + } + } + + static long markSubscriptionSet(PublishSubscriber instance) { + for (;;) { + long state = instance.state; + + if (isCancelled(state)) { + return state; + } + + if (STATE.compareAndSet(instance, state, state | SUBSCRIPTION_SET_FLAG)) { + return state; + } + } + } + + static long markSubscriptionSetAndAddWork(PublishSubscriber instance) { + for (;;) { + long state = instance.state; + + if (isCancelled(state)) { + return state; + } + + long nextState = addWork(state); + if (STATE.compareAndSet(instance, state, nextState | SUBSCRIPTION_SET_FLAG)) { + return state; + } + } + } + + static long markCancelled(PublishSubscriber instance) { + for (;;) { + long state = instance.state; + + if (isCancelled(state)) { + return state; + } + + long nextState = addWork(state); + if (STATE.compareAndSet(instance, state, nextState | CANCELLED_FLAG)) { + return state; + } + } + } + + static long markWorkDone(PublishSubscriber instance, long expectedState) { + for (;;) { + long state = instance.state; + + if (expectedState != state) { + return state; + } + + long nextState = state & ~WORK_IN_PROGRESS_MASK; + if (STATE.compareAndSet(instance, state, nextState)) { + return nextState; + } + } + } + + static boolean isConnected(long state) { + return (state & CONNECTED_FLAG) == CONNECTED_FLAG; + } + + static boolean isFinalized(long state) { + return (state & FINALIZED_FLAG) == FINALIZED_FLAG; + } + + static boolean isCancelled(long state) { + return (state & CANCELLED_FLAG) == CANCELLED_FLAG; + } + + static boolean isTerminated(long state) { + return (state & TERMINATED_FLAG) == TERMINATED_FLAG; + } + + static boolean isSubscriptionSet(long state) { + return (state & SUBSCRIPTION_SET_FLAG) == SUBSCRIPTION_SET_FLAG; + } + + static boolean hasWorkInProgress(long state) { + return (state & WORK_IN_PROGRESS_MASK) > 0; + } + + static final long FINALIZED_FLAG = + 0b1000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000L; + + static final long CANCELLED_FLAG = + 0b0010_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000L; + + static final long TERMINATED_FLAG = + 0b0100_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000L; + + static final long SUBSCRIPTION_SET_FLAG = + 0b0000_1000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000L; + + static final long CONNECTED_FLAG = + 0b0000_0100_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000L; + + static final long WORK_IN_PROGRESS_MASK = + 0b0000_0000_0000_0000_0000_0000_0000_0000_1111_1111_1111_1111_1111_1111_1111_1111L; } static abstract class PubSubInner implements InnerProducer { @@ -631,7 +917,7 @@ void drainParent() { PublishSubscriber p = parent; if (p != null) { - p.drain(); + p.drainFromInner(); } } @@ -640,7 +926,7 @@ PublishSubscriber p = parent; if (p != null) { p.remove(this); - p.drain(); + p.drainFromInner(); } } @@ -654,4 +940,4 @@ return super.scanUnsafe(key); } } -} +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxRefCount.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxRefCount.java (.../FluxRefCount.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxRefCount.java (.../FluxRefCount.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -62,7 +62,10 @@ @Override public void subscribe(CoreSubscriber actual) { RefCountMonitor conn; + RefCountInner inner = new RefCountInner<>(actual); + source.subscribe(inner); + boolean connect = false; synchronized (this) { conn = connection; @@ -79,7 +82,7 @@ } } - source.subscribe(new RefCountInner<>(actual, conn)); + inner.setRefCountMonitor(conn); if (connect) { source.connect(conn); @@ -162,26 +165,33 @@ implements QueueSubscription, InnerOperator { final CoreSubscriber actual; - final RefCountMonitor connection; + RefCountMonitor connection; Subscription s; QueueSubscription qs; - volatile int parentDone; //used to guard against doubly terminating subscribers (eg. double cancel) - static final AtomicIntegerFieldUpdater PARENT_DONE = - AtomicIntegerFieldUpdater.newUpdater(RefCountInner.class, "parentDone"); + Throwable error; - RefCountInner(CoreSubscriber actual, RefCountMonitor connection) { + + static final int MONITOR_SET_FLAG = 0b0010_0000_0000_0000_0000_0000_0000_0000; + static final int TERMINATED_FLAG = 0b0100_0000_0000_0000_0000_0000_0000_0000; + static final int CANCELLED_FLAG = 0b1000_0000_0000_0000_0000_0000_0000_0000; + + volatile int state; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater STATE = + AtomicIntegerFieldUpdater.newUpdater(RefCountInner.class, "state"); + + RefCountInner(CoreSubscriber actual) { this.actual = actual; - this.connection = connection; } @Override @Nullable public Object scanUnsafe(Attr key) { - if (key == Attr. PARENT) return s; - if (key == Attr.TERMINATED) return parentDone == 1; - if (key == Attr.CANCELLED) return parentDone == 2; + if (key == Attr.PARENT) return s; + if (key == Attr.TERMINATED) return isTerminated(state); + if (key == Attr.CANCELLED) return isCancelled(state); if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; return InnerOperator.super.scanUnsafe(key); @@ -191,31 +201,80 @@ public void onSubscribe(Subscription s) { if (Operators.validate(this.s, s)) { this.s = s; - actual.onSubscribe(this); } } + void setRefCountMonitor(RefCountMonitor connection) { + this.connection = connection; + this.actual.onSubscribe(this); + + for (;;) { + int previousState = this.state; + + if (isCancelled(previousState)) { + return; + } + + if (isTerminated(previousState)) { + connection.upstreamFinished(); + Throwable e = this.error; + if (e != null) { + this.actual.onError(e); + } + else { + this.actual.onComplete(); + } + return; + } + + if (STATE.compareAndSet(this, previousState, previousState | MONITOR_SET_FLAG)) { + return; + } + } + } + @Override public void onNext(T t) { actual.onNext(t); } @Override public void onError(Throwable t) { - if (PARENT_DONE.compareAndSet(this, 0, 1)) { - connection.upstreamFinished(); - actual.onError(t); + this.error = t; + for (;;) { + int previousState = this.state; + + if (isTerminated(previousState) || isCancelled(previousState)) { + Operators.onErrorDropped(t, actual.currentContext()); + return; + } + + if (STATE.compareAndSet(this, previousState, previousState | TERMINATED_FLAG)) { + if (isMonitorSet(previousState)) { + connection.upstreamFinished(); + actual.onError(t); + } + return; + } } - else { - Operators.onErrorDropped(t, actual.currentContext()); - } } @Override public void onComplete() { - if (PARENT_DONE.compareAndSet(this, 0, 1)) { - connection.upstreamFinished(); - actual.onComplete(); + for (;;) { + int previousState = this.state; + + if (isTerminated(previousState) || isCancelled(previousState)) { + return; + } + + if (STATE.compareAndSet(this, previousState, previousState | TERMINATED_FLAG)) { + if (isMonitorSet(previousState)) { + connection.upstreamFinished(); + actual.onComplete(); + } + return; + } } } @@ -227,7 +286,14 @@ @Override public void cancel() { s.cancel(); - if (PARENT_DONE.compareAndSet(this, 0, 2)) { + + int previousState = this.state; + + if (isTerminated(previousState) || isCancelled(previousState)) { + return; + } + + if (STATE.compareAndSet(this, previousState, previousState | CANCELLED_FLAG)) { connection.innerCancelled(); } } @@ -267,5 +333,18 @@ public void clear() { qs.clear(); } + + + static boolean isTerminated(int state) { + return (state & TERMINATED_FLAG) == TERMINATED_FLAG; + } + + static boolean isCancelled(int state) { + return (state & CANCELLED_FLAG) == CANCELLED_FLAG; + } + + static boolean isMonitorSet(int state) { + return (state & MONITOR_SET_FLAG) == MONITOR_SET_FLAG; + } } } Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxRefCountGrace.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxRefCountGrace.java (.../FluxRefCountGrace.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxRefCountGrace.java (.../FluxRefCountGrace.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2017-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -71,7 +71,10 @@ @Override public void subscribe(CoreSubscriber actual) { RefConnection conn; + RefCountInner inner = new RefCountInner<>(actual, this); + source.subscribe(inner); + boolean connect = false; synchronized (this) { conn = connection; @@ -91,7 +94,7 @@ } } - source.subscribe(new RefCountInner<>(actual, this, conn)); + inner.setRefConnection(conn); if (connect) { source.connect(conn); @@ -196,20 +199,55 @@ final FluxRefCountGrace parent; - final RefConnection connection; + RefConnection connection; Subscription s; QueueSubscription qs; - volatile int parentDone; - static final AtomicIntegerFieldUpdater PARENT_DONE = - AtomicIntegerFieldUpdater.newUpdater(RefCountInner.class, "parentDone"); + Throwable error; - RefCountInner(CoreSubscriber actual, FluxRefCountGrace parent, - RefConnection connection) { + + static final int MONITOR_SET_FLAG = 0b0010_0000_0000_0000_0000_0000_0000_0000; + static final int TERMINATED_FLAG = 0b0100_0000_0000_0000_0000_0000_0000_0000; + static final int CANCELLED_FLAG = 0b1000_0000_0000_0000_0000_0000_0000_0000; + + volatile int state; //used to guard against doubly terminating subscribers (e.g. double cancel) + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater STATE = + AtomicIntegerFieldUpdater.newUpdater(RefCountInner.class, "state"); + + RefCountInner(CoreSubscriber actual, FluxRefCountGrace parent) { this.actual = actual; this.parent = parent; + } + + void setRefConnection(RefConnection connection) { this.connection = connection; + this.actual.onSubscribe(this); + + for (;;) { + int previousState = this.state; + + if (isCancelled(previousState)) { + return; + } + + if (isTerminated(previousState)) { + this.parent.terminated(connection); + Throwable e = this.error; + if (e != null) { + this.actual.onError(e); + } + else { + this.actual.onComplete(); + } + return; + } + + if (STATE.compareAndSet(this, previousState, previousState | MONITOR_SET_FLAG)) { + return; + } + } } @Override @@ -219,18 +257,42 @@ @Override public void onError(Throwable t) { - if (PARENT_DONE.compareAndSet(this, 0, 1)) { - parent.terminated(connection); + this.error = t; + for (;;) { + int previousState = this.state; + + if (isTerminated(previousState) || isCancelled(previousState)) { + Operators.onErrorDropped(t, actual.currentContext()); + return; + } + + if (STATE.compareAndSet(this, previousState, previousState | TERMINATED_FLAG)) { + if (isMonitorSet(previousState)) { + this.parent.terminated(connection); + this.actual.onError(t); + } + return; + } } - actual.onError(t); } @Override public void onComplete() { - if (PARENT_DONE.compareAndSet(this, 0, 1)) { - parent.terminated(connection); + for (;;) { + int previousState = this.state; + + if (isTerminated(previousState) || isCancelled(previousState)) { + return; + } + + if (STATE.compareAndSet(this, previousState, previousState | TERMINATED_FLAG)) { + if (isMonitorSet(previousState)) { + this.parent.terminated(connection); + this.actual.onComplete(); + } + return; + } } - actual.onComplete(); } @Override @@ -241,7 +303,14 @@ @Override public void cancel() { s.cancel(); - if (PARENT_DONE.compareAndSet(this, 0, 1)) { + + int previousState = this.state; + + if (isTerminated(previousState) || isCancelled(previousState)) { + return; + } + + if (STATE.compareAndSet(this, previousState, previousState | CANCELLED_FLAG)) { parent.cancel(connection); } } @@ -250,8 +319,6 @@ public void onSubscribe(Subscription s) { if (Operators.validate(this.s, s)) { this.s = s; - - actual.onSubscribe(this); } } @@ -294,8 +361,24 @@ @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.PARENT) return s; + if (key == Attr.TERMINATED) return isTerminated(state); + if (key == Attr.CANCELLED) return isCancelled(state); + return InnerOperator.super.scanUnsafe(key); } + + static boolean isTerminated(int state) { + return (state & TERMINATED_FLAG) == TERMINATED_FLAG; + } + + static boolean isCancelled(int state) { + return (state & CANCELLED_FLAG) == CANCELLED_FLAG; + } + + static boolean isMonitorSet(int state) { + return (state & MONITOR_SET_FLAG) == MONITOR_SET_FLAG; + } } } Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxReplay.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxReplay.java (.../FluxReplay.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxReplay.java (.../FluxReplay.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -138,6 +138,15 @@ this.value = value; this.time = time; } + + @Override + public String toString() { + return "TimedNode{" + + "index=" + index + + ", value=" + value + + ", time=" + time + + '}'; + } } final int limit; @@ -423,11 +432,11 @@ @Override public void add(T value) { final TimedNode tail = this.tail; - final TimedNode n = new TimedNode<>(tail.index + 1, + final TimedNode valueNode = new TimedNode<>(tail.index + 1, value, scheduler.now(TimeUnit.NANOSECONDS)); - tail.set(n); - this.tail = n; + tail.set(valueNode); + this.tail = valueNode; int s = size; if (s == limit) { head = head.get(); @@ -437,6 +446,16 @@ } long limit = scheduler.now(TimeUnit.NANOSECONDS) - maxAge; + //short-circuit if maxAge == 0: no sense in keeping any old value. + //we still want to keep the newly added value in order for the immediately following replay + //to propagate it to currently registered subscribers. + if (maxAge == 0) { + head = valueNode; + return; + } + + //otherwise, start walking the linked list in order to find the first node > time limit + //(in which case we'll have passed all nodes that have reached the TTL). TimedNode h = head; TimedNode next; int removed = 0; @@ -446,7 +465,9 @@ break; } - if (next.time > limit) { + if (next.time > limit || next == valueNode) { + //next == valueNode case causes head swap and actual removal, even if its time cannot be > limit. + //otherwise we'd skip removal and re-walk the whole linked list on next add, retaining outdated values for nothing. if (removed != 0) { size = size - removed; head = h; @@ -1188,7 +1209,7 @@ @Override @Nullable - public Object scanUnsafe(Scannable.Attr key) { + public Object scanUnsafe(Attr key) { if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.PARENT) return source; if (key == Attr.RUN_ON) return scheduler; @@ -1840,4 +1861,4 @@ REQUESTED.addAndGet(this, -n); } } -} +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxSink.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxSink.java (.../FluxSink.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxSink.java (.../FluxSink.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ import reactor.core.CoreSubscriber; import reactor.core.Disposable; import reactor.util.context.Context; +import reactor.util.context.ContextView; /** * Wrapper API around a downstream Subscriber for emitting any number of @@ -69,11 +70,24 @@ * operator or directly by a child subscriber overriding * {@link CoreSubscriber#currentContext()} * - * @return the current subscriber {@link Context}. + * @deprecated To be removed in 3.6.0 at the earliest. Prefer using #contextView() instead. */ + @Deprecated Context currentContext(); + /** + * Return the current subscriber's context as a {@link ContextView} for inspection. + *

      + * {@link Context} can be enriched downstream via {@link Flux#contextWrite(Function)} + * operator or directly by a child subscriber overriding {@link CoreSubscriber#currentContext()}. + * + * @return the current subscriber {@link ContextView}. + */ + default ContextView contextView() { + return currentContext(); + } + /** * The current outstanding request amount. * @return the current outstanding request amount @@ -91,15 +105,20 @@ * any request to this sink. *

      * For push/pull sinks created using {@link Flux#create(java.util.function.Consumer)} - * or {@link Flux#create(java.util.function.Consumer, FluxSink.OverflowStrategy)}, + * or {@link Flux#create(java.util.function.Consumer, OverflowStrategy)}, * the consumer * is invoked for every request to enable a hybrid backpressure-enabled push/pull model. + *

      + * Note: in case of multiple {@link Subscription#request} happening + * concurrently to this method, the first consumer invocation may process + * accumulated demand instead of being called multiple times. + *

      * When bridging with asynchronous listener-based APIs, the {@code onRequest} callback * may be used to request more data from source if required and to manage backpressure * by delivering data to sink only when requests are pending. *

      * For push-only sinks created using {@link Flux#push(java.util.function.Consumer)} - * or {@link Flux#push(java.util.function.Consumer, FluxSink.OverflowStrategy)}, + * or {@link Flux#push(java.util.function.Consumer, OverflowStrategy)}, * the consumer is invoked with an initial request of {@code Long.MAX_VALUE} when this method * is invoked. * @@ -168,4 +187,4 @@ */ BUFFER } -} +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxSource.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxSource.java (.../FluxSource.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxSource.java (.../FluxSource.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,12 +18,16 @@ import java.util.Objects; +import io.micrometer.context.ContextSnapshot; import org.reactivestreams.Publisher; +import org.reactivestreams.Subscription; import reactor.core.CorePublisher; import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; import reactor.core.Scannable; import reactor.util.annotation.Nullable; +import reactor.util.context.Context; /** * A connecting {@link Flux} Publisher (right-to-left from a composition chain perspective) @@ -64,7 +68,11 @@ @Override @SuppressWarnings("unchecked") public void subscribe(CoreSubscriber actual) { - source.subscribe(actual); + if (ContextPropagationSupport.shouldPropagateContextToThreadLocals()) { + source.subscribe(new FluxSourceRestoringThreadLocalsSubscriber<>(actual)); + } else { + source.subscribe(actual); + } } @Override @@ -91,4 +99,94 @@ return null; } + static final class FluxSourceRestoringThreadLocalsSubscriber + implements Fuseable.ConditionalSubscriber, InnerConsumer { + + final CoreSubscriber actual; + final Fuseable.ConditionalSubscriber actualConditional; + + Subscription s; + + @SuppressWarnings("unchecked") + FluxSourceRestoringThreadLocalsSubscriber(CoreSubscriber actual) { + this.actual = actual; + if (actual instanceof Fuseable.ConditionalSubscriber) { + this.actualConditional = (Fuseable.ConditionalSubscriber) actual; + } + else { + this.actualConditional = null; + } + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) { + return s; + } + if (key == Attr.RUN_STYLE) { + return Attr.RunStyle.SYNC; + } + if (key == Attr.ACTUAL) { + return actual; + } + return null; + } + + @Override + public Context currentContext() { + return actual.currentContext(); + } + + @SuppressWarnings("try") + @Override + public void onSubscribe(Subscription s) { + // This is needed, as the downstream can then switch threads, + // continue the subscription using different primitives and omit this operator + try (ContextSnapshot.Scope ignored = + ContextPropagation.setThreadLocals(actual.currentContext())) { + actual.onSubscribe(s); + } + } + + @SuppressWarnings("try") + @Override + public void onNext(T t) { + try (ContextSnapshot.Scope ignored = + ContextPropagation.setThreadLocals(actual.currentContext())) { + actual.onNext(t); + } + } + + @SuppressWarnings("try") + @Override + public boolean tryOnNext(T t) { + try (ContextSnapshot.Scope ignored = + ContextPropagation.setThreadLocals(actual.currentContext())) { + if (actualConditional != null) { + return actualConditional.tryOnNext(t); + } + actual.onNext(t); + return true; + } + } + + @SuppressWarnings("try") + @Override + public void onError(Throwable t) { + try (ContextSnapshot.Scope ignored = + ContextPropagation.setThreadLocals(actual.currentContext())) { + actual.onError(t); + } + } + + @SuppressWarnings("try") + @Override + public void onComplete() { + try (ContextSnapshot.Scope ignored = + ContextPropagation.setThreadLocals(actual.currentContext())) { + actual.onComplete(); + } + } + } } Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxSourceFuseable.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxSourceFuseable.java (.../FluxSourceFuseable.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxSourceFuseable.java (.../FluxSourceFuseable.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -77,10 +77,10 @@ @Override @Nullable - public Object scanUnsafe(Scannable.Attr key) { - if (key == Scannable.Attr.PREFETCH) return getPrefetch(); - if (key == Scannable.Attr.PARENT) return source; + public Object scanUnsafe(Attr key) { + if (key == Attr.PREFETCH) return getPrefetch(); + if (key == Attr.PARENT) return source; if (key == Attr.RUN_STYLE) return Scannable.from(source).scanUnsafe(key); return null; } -} +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxStream.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxStream.java (.../FluxStream.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxStream.java (.../FluxStream.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,10 +16,8 @@ package reactor.core.publisher; -import java.util.Iterator; import java.util.Objects; import java.util.Spliterator; -import java.util.Spliterators; import java.util.function.Supplier; import java.util.stream.Stream; @@ -53,13 +51,13 @@ return; } - Iterator it; + Spliterator sp; boolean knownToBeFinite; try { Spliterator spliterator = Objects.requireNonNull(stream.spliterator(), "The stream returned a null Spliterator"); knownToBeFinite = spliterator.hasCharacteristics(Spliterator.SIZED); - it = Spliterators.iterator(spliterator); //this is the default for BaseStream#iterator() anyway + sp = spliterator; //this is the default for BaseStream#iterator() anyway } catch (Throwable e) { Operators.error(actual, Operators.onOperatorError(e, actual.currentContext())); @@ -68,7 +66,7 @@ //although not required by AutoCloseable, Stream::close SHOULD be idempotent //(at least the default AbstractPipeline implementation is) - FluxIterable.subscribe(actual, it, knownToBeFinite, stream::close); + FluxIterable.subscribe(actual, sp, knownToBeFinite, stream::close); } @Override Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxSubscribeOnValue.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxSubscribeOnValue.java (.../FluxSubscribeOnValue.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxSubscribeOnValue.java (.../FluxSubscribeOnValue.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -119,7 +119,7 @@ @Override @Nullable - public Object scanUnsafe(Scannable.Attr key) { + public Object scanUnsafe(Attr key) { if (key == Attr.CANCELLED) { return future == OperatorDisposables.DISPOSED; } @@ -298,4 +298,4 @@ } } -} +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxSwitchMapNoPrefetch.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxSwitchMapNoPrefetch.java (.../FluxSwitchMapNoPrefetch.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxSwitchMapNoPrefetch.java (.../FluxSwitchMapNoPrefetch.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2021-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package reactor.core.publisher; import java.util.Objects; +import java.util.StringJoiner; import java.util.concurrent.atomic.AtomicLongFieldUpdater; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import java.util.function.Function; @@ -72,6 +73,9 @@ static final class SwitchMapMain implements InnerOperator { + @Nullable + final StateLogger logger; + final Function> mapper; final CoreSubscriber actual; @@ -98,8 +102,15 @@ SwitchMapMain(CoreSubscriber actual, Function> mapper) { + this(actual, mapper, null); + } + + SwitchMapMain(CoreSubscriber actual, + Function> mapper, + @Nullable StateLogger logger) { this.actual = actual; this.mapper = mapper; + this.logger = logger; } @Override @@ -148,14 +159,14 @@ final boolean hasInner = si != null; if (!hasInner) { - final SwitchMapInner nsi = new SwitchMapInner<>(this, this.actual, 0); + final SwitchMapInner nsi = new SwitchMapInner<>(this, this.actual, 0, this.logger); this.inner = nsi; subscribeInner(t, nsi, 0); return; } final int nextIndex = si.index + 1; - final SwitchMapInner nsi = new SwitchMapInner<>(this, this.actual, nextIndex); + final SwitchMapInner nsi = new SwitchMapInner<>(this, this.actual, nextIndex, this.logger); this.inner = nsi; si.nextInner = nsi; @@ -299,6 +310,9 @@ static final class SwitchMapInner implements InnerConsumer { + @Nullable + final StateLogger logger; + final SwitchMapMain parent; final CoreSubscriber actual; @@ -312,10 +326,11 @@ T nextElement; SwitchMapInner nextInner; - SwitchMapInner(SwitchMapMain parent, CoreSubscriber actual, int index) { + SwitchMapInner(SwitchMapMain parent, CoreSubscriber actual, int index, @Nullable StateLogger logger) { this.parent = parent; this.actual = actual; this.index = index; + this.logger = logger; } @Override @@ -562,6 +577,14 @@ void cancelFromParent() { this.s.cancel(); } + + @Override + public String toString() { + return new StringJoiner(", ", + SwitchMapInner.class.getSimpleName() + "[", + "]").add("index=" + index) + .toString(); + } } static int INDEX_OFFSET = 32; @@ -595,6 +618,9 @@ } if (SwitchMapMain.STATE.compareAndSet(instance, state, TERMINATED)) { + if (instance.logger != null) { + instance.logger.log(instance.toString(), "std", state, TERMINATED); + } return state; } } @@ -620,7 +646,11 @@ return state; } - if (SwitchMapMain.STATE.compareAndSet(instance, state, state | COMPLETED_MASK)) { + final long nextState = state | COMPLETED_MASK; + if (SwitchMapMain.STATE.compareAndSet(instance, state, nextState)) { + if (instance.logger != null) { + instance.logger.log(instance.toString(), "smc", state, nextState); + } return state; } } @@ -671,6 +701,9 @@ hasMainCompleted(state), // keep main completed flag as is hasInnerCompleted(state)); // keep inner completed flag as is if (SwitchMapMain.STATE.compareAndSet(instance, state, nextState)) { + if (instance.logger != null) { + instance.logger.log(instance.toString(), "adr", state, nextState); + } return nextState; } } @@ -696,14 +729,16 @@ int nextIndex = nextIndex(state); for (; ; ) { - if (SwitchMapMain.STATE.compareAndSet(instance, - state, - state(nextIndex, // set actual index to the next index - isWip(state), // keep WIP flag unchanged - hasRequest(state), // keep requested as is - false, // unset inner subscribed flag - false, // main completed flag can not be set at this stage - false))) { // set inner completed to false since another inner comes in + final long nextState = state(nextIndex, // set actual index to the next index + isWip(state), // keep WIP flag unchanged + hasRequest(state), // keep requested as is + false, // unset inner subscribed flag + false, // main completed flag can not be set at this stage + false);// set inner completed to false since another inner comes in + if (SwitchMapMain.STATE.compareAndSet(instance, state, nextState)) { + if (instance.logger != null) { + instance.logger.log(instance.toString(), "ini", state, nextState); + } return state; } @@ -740,14 +775,19 @@ return state; } + final long nextState = state(expectedIndex, // keep expected index + false, // wip assumed to be false + hasRequest(state), // keep existing request state + true, // set inner subscribed bit + hasMainCompleted(state), // keep main completed flag as is + false);// inner can not be completed at this phase if (SwitchMapMain.STATE.compareAndSet(instance, - state, - state(expectedIndex, // keep expected index - false, // wip assumed to be false - hasRequest(state), // keep existing request state - true, // set inner subscribed bit - hasMainCompleted(state), // keep main completed flag as is - false))) { // inner can not be completed at this phase + state, nextState)) { + + if (instance.logger != null) { + instance.logger.log(instance.toString(), "sns", state, nextState); + } + return state; } } @@ -777,14 +817,16 @@ return state; } - if (SwitchMapMain.STATE.compareAndSet(instance, - state, - state(expectedIndex, // keep expected index - true, // set wip bit - hasRequest(state), // keep request as is - true, // assumed inner subscribed if index has not changed - hasMainCompleted(state), // keep main completed flag as is - false))) { // inner can not be completed at this phase + final long nextState = state(expectedIndex, // keep expected index + true, // set wip bit + hasRequest(state), // keep request as is + true, // assumed inner subscribed if index has not changed + hasMainCompleted(state), // keep main completed flag as is + false);// inner can not be completed at this phase + if (SwitchMapMain.STATE.compareAndSet(instance, state, nextState)) { + if (instance.logger != null) { + instance.logger.log(instance.toString(), "swp", state, nextState); + } return state; } } @@ -822,14 +864,17 @@ return state; } - if (SwitchMapMain.STATE.compareAndSet(instance, - state, - state(actualIndex, // set actual index; assumed index may change and if we have done with the work we have to unset WIP flag - false, // unset wip flag - isDemandFulfilled && expectedRequest == actualRequest ? 0 : actualRequest, // set hasRequest to 0 if we know that demand was fulfilled an expectedRequest is equal to the actual one (so it has not changed) - isInnerSubscribed(state), // keep inner state unchanged; if index has changed the flag is unset, otherwise it is set - hasMainCompleted(state), // keep main completed flag as it is - false))) { // this flag is always unset in this method since we do call from Inner#OnNext + final long nextState = state( + actualIndex,// set actual index; assumed index may change and if we have done with the work we have to unset WIP flag + false,// unset wip flag + isDemandFulfilled && expectedRequest == actualRequest ? 0 : actualRequest,// set hasRequest to 0 if we know that demand was fulfilled an expectedRequest is equal to the actual one (so it has not changed) + isInnerSubscribed(state),// keep inner state unchanged; if index has changed the flag is unset, otherwise it is set + hasMainCompleted(state),// keep main completed flag as it is + false); // this flag is always unset in this method since we do call from Inner#OnNext + if (SwitchMapMain.STATE.compareAndSet(instance, state, nextState)) { + if (instance.logger != null) { + instance.logger.log(instance.toString(), "uwp", state, nextState); + } return state; } } @@ -852,14 +897,16 @@ } final boolean isInnerSubscribed = isInnerSubscribed(state); - if (SwitchMapMain.STATE.compareAndSet(instance, - state, - state(index(state), // keep the index unchanged - false, // unset WIP flag - hasRequest(state), // keep hasRequest flag as is - isInnerSubscribed, // keep inner subscribed flag as is - hasMainCompleted(state), // keep main completed flag as is - isInnerSubscribed))) { // if index has changed then inner subscribed remains set, thus we are safe to set inner completed flag + final long nextState = state(index(state), // keep the index unchanged + false, // unset WIP flag + hasRequest(state), // keep hasRequest flag as is + isInnerSubscribed, // keep inner subscribed flag as is + hasMainCompleted(state), // keep main completed flag as is + isInnerSubscribed); // if index has changed then inner subscribed remains set, thus we are safe to set inner completed flag + if (SwitchMapMain.STATE.compareAndSet(instance, state, nextState)) { + if (instance.logger != null) { + instance.logger.log(instance.toString(), "sic", state, nextState); + } return state; } } Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxSwitchOnFirst.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxSwitchOnFirst.java (.../FluxSwitchOnFirst.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxSwitchOnFirst.java (.../FluxSwitchOnFirst.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2018-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -57,7 +57,8 @@ if (actual instanceof Fuseable.ConditionalSubscriber) { return new SwitchOnFirstConditionalMain<>((Fuseable.ConditionalSubscriber) actual, transformer, - cancelSourceOnComplete); + cancelSourceOnComplete, + null); } return new SwitchOnFirstMain<>(actual, transformer, cancelSourceOnComplete); } @@ -108,7 +109,11 @@ return state; } - if (AbstractSwitchOnFirstMain.STATE.compareAndSet(instance, state, state | HAS_FIRST_VALUE_RECEIVED_FLAG)) { + final int nextState = state | HAS_FIRST_VALUE_RECEIVED_FLAG; + if (AbstractSwitchOnFirstMain.STATE.compareAndSet(instance, state, nextState)) { + if (instance.logger != null) { + instance.logger.log(instance.toString(), "mfvr", state, nextState); + } return state; } } @@ -128,7 +133,11 @@ return state; } - if (AbstractSwitchOnFirstMain.STATE.compareAndSet(instance, state, state | HAS_INBOUND_SUBSCRIBED_ONCE_FLAG)) { + final int nextState = state | HAS_INBOUND_SUBSCRIBED_ONCE_FLAG; + if (AbstractSwitchOnFirstMain.STATE.compareAndSet(instance, state, nextState)) { + if (instance.logger != null) { + instance.logger.log(instance.toString(), "miso", state, nextState); + } return state; } } @@ -148,7 +157,11 @@ return state; } - if (AbstractSwitchOnFirstMain.STATE.compareAndSet(instance, state, state | HAS_INBOUND_SUBSCRIBER_SET_FLAG)) { + final int nextState = state | HAS_INBOUND_SUBSCRIBER_SET_FLAG; + if (AbstractSwitchOnFirstMain.STATE.compareAndSet(instance, state, nextState)) { + if (instance.logger != null) { + instance.logger.log(instance.toString(), "miss", state, nextState); + } return state; } } @@ -168,7 +181,11 @@ return state; } - if (AbstractSwitchOnFirstMain.STATE.compareAndSet(instance, state, state | HAS_INBOUND_REQUESTED_ONCE_FLAG)) { + int nextState = state | HAS_INBOUND_REQUESTED_ONCE_FLAG; + if (AbstractSwitchOnFirstMain.STATE.compareAndSet(instance, state, nextState)) { + if (instance.logger != null) { + instance.logger.log(instance.toString(), "miro", state, nextState); + } return state; } } @@ -188,7 +205,11 @@ return state; } - if (AbstractSwitchOnFirstMain.STATE.compareAndSet(instance, state, state | HAS_FIRST_VALUE_SENT_FLAG)) { + final int nextState = state | HAS_FIRST_VALUE_SENT_FLAG; + if (AbstractSwitchOnFirstMain.STATE.compareAndSet(instance, state, nextState)) { + if (instance.logger != null) { + instance.logger.log(instance.toString(), "mfvs", state, nextState); + } return state; } } @@ -208,7 +229,11 @@ return state; } - if (AbstractSwitchOnFirstMain.STATE.compareAndSet(instance, state, state | HAS_INBOUND_TERMINATED_FLAG)) { + final int nextState = state | HAS_INBOUND_TERMINATED_FLAG; + if (AbstractSwitchOnFirstMain.STATE.compareAndSet(instance, state, nextState)) { + if (instance.logger != null) { + instance.logger.log(instance.toString(), "mitd", state, nextState); + } return state; } } @@ -228,7 +253,11 @@ return state; } - if (AbstractSwitchOnFirstMain.STATE.compareAndSet(instance, state, state | HAS_INBOUND_CANCELLED_FLAG)) { + final int nextState = state | HAS_INBOUND_CANCELLED_FLAG; + if (AbstractSwitchOnFirstMain.STATE.compareAndSet(instance, state, nextState)) { + if (instance.logger != null) { + instance.logger.log(instance.toString(), "micd", state, nextState); + } return state; } } @@ -248,7 +277,11 @@ return state; } - if (AbstractSwitchOnFirstMain.STATE.compareAndSet(instance, state, state | HAS_INBOUND_CLOSED_PREMATURELY_FLAG)) { + final int nextState = state | HAS_INBOUND_CLOSED_PREMATURELY_FLAG; + if (AbstractSwitchOnFirstMain.STATE.compareAndSet(instance, state, nextState)) { + if (instance.logger != null) { + instance.logger.log(instance.toString(), "micp", state, nextState); + } return state; } } @@ -273,7 +306,12 @@ return state; } - if (AbstractSwitchOnFirstMain.STATE.compareAndSet(instance, state, state | HAS_INBOUND_CANCELLED_FLAG | HAS_OUTBOUND_TERMINATED_FLAG)) { + final int nextState = + state | HAS_INBOUND_CANCELLED_FLAG | HAS_OUTBOUND_TERMINATED_FLAG; + if (AbstractSwitchOnFirstMain.STATE.compareAndSet(instance, state, nextState)) { + if (instance.logger != null) { + instance.logger.log(instance.toString(), "icot", state, nextState); + } return state; } } @@ -293,7 +331,11 @@ return state; } - if (AbstractSwitchOnFirstMain.STATE.compareAndSet(instance, state, state | HAS_OUTBOUND_SUBSCRIBED_FLAG)) { + final int nextState = state | HAS_OUTBOUND_SUBSCRIBED_FLAG; + if (AbstractSwitchOnFirstMain.STATE.compareAndSet(instance, state, nextState)) { + if (instance.logger != null) { + instance.logger.log(instance.toString(), "mosd", state, nextState); + } return state; } } @@ -314,7 +356,11 @@ return state; } - if (AbstractSwitchOnFirstMain.STATE.compareAndSet(instance, state, state | HAS_OUTBOUND_TERMINATED_FLAG)) { + final int nextState = state | HAS_OUTBOUND_TERMINATED_FLAG; + if (AbstractSwitchOnFirstMain.STATE.compareAndSet(instance, state, nextState)) { + if (instance.logger != null) { + instance.logger.log(instance.toString(), "motd", state, nextState); + } return state; } } @@ -334,7 +380,11 @@ return state; } - if (AbstractSwitchOnFirstMain.STATE.compareAndSet(instance, state, state | HAS_OUTBOUND_CANCELLED_FLAG)) { + final int nextState = state | HAS_OUTBOUND_CANCELLED_FLAG; + if (AbstractSwitchOnFirstMain.STATE.compareAndSet(instance, state, nextState)) { + if (instance.logger != null) { + instance.logger.log(instance.toString(), "mocd", state, nextState); + } return state; } } @@ -387,7 +437,11 @@ static abstract class AbstractSwitchOnFirstMain extends Flux implements InnerOperator { - final SwitchOnFirstControlSubscriber outboundSubscriber; + @Nullable + final StateLogger logger; + + final SwitchOnFirstControlSubscriber + outboundSubscriber; final BiFunction, Flux, Publisher> transformer; Subscription s; @@ -409,14 +463,16 @@ @SuppressWarnings("unchecked") AbstractSwitchOnFirstMain(CoreSubscriber outboundSubscriber, BiFunction, Flux, Publisher> transformer, - boolean cancelSourceOnComplete) { + boolean cancelSourceOnComplete, + @Nullable StateLogger logger) { this.outboundSubscriber = outboundSubscriber instanceof Fuseable.ConditionalSubscriber ? new SwitchOnFirstConditionalControlSubscriber<>(this, (Fuseable.ConditionalSubscriber) outboundSubscriber, cancelSourceOnComplete) : new SwitchOnFirstControlSubscriber<>(this, outboundSubscriber, cancelSourceOnComplete); this.transformer = transformer; + this.logger = logger; } @Override @@ -721,12 +777,20 @@ static final class SwitchOnFirstMain extends AbstractSwitchOnFirstMain { + SwitchOnFirstMain(CoreSubscriber outer, BiFunction, Flux, Publisher> transformer, boolean cancelSourceOnComplete) { - super(outer, transformer, cancelSourceOnComplete); + super(outer, transformer, cancelSourceOnComplete, null); } + SwitchOnFirstMain(CoreSubscriber outer, + BiFunction, Flux, Publisher> transformer, + boolean cancelSourceOnComplete, + @Nullable StateLogger logger) { + super(outer, transformer, cancelSourceOnComplete, logger); + } + @Override CoreSubscriber convert(CoreSubscriber inboundSubscriber) { return inboundSubscriber; @@ -746,9 +810,16 @@ SwitchOnFirstConditionalMain(Fuseable.ConditionalSubscriber outer, BiFunction, Flux, Publisher> transformer, boolean cancelSourceOnComplete) { - super(outer, transformer, cancelSourceOnComplete); + super(outer, transformer, cancelSourceOnComplete, null); } + SwitchOnFirstConditionalMain(Fuseable.ConditionalSubscriber outer, + BiFunction, Flux, Publisher> transformer, + boolean cancelSourceOnComplete, + @Nullable StateLogger logger) { + super(outer, transformer, cancelSourceOnComplete, logger); + } + @Override CoreSubscriber convert(CoreSubscriber inboundSubscriber) { return Operators.toConditionalSubscriber(inboundSubscriber); Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxTakeUntil.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxTakeUntil.java (.../FluxTakeUntil.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxTakeUntil.java (.../FluxTakeUntil.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -81,18 +81,19 @@ return; } - actual.onNext(t); - boolean b; try { b = predicate.test(t); } catch (Throwable e) { + actual.onNext(t); onError(Operators.onOperatorError(s, e, t, actual.currentContext())); return; } + actual.onNext(t); + if (b) { s.cancel(); Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxTakeUntilOther.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxTakeUntilOther.java (.../FluxTakeUntilOther.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxTakeUntilOther.java (.../FluxTakeUntilOther.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -99,7 +99,14 @@ @Override public void onNext(U t) { - onComplete(); + if (once) { + return; + } + once = true; + //in case more values are coming, cancel main.other (which is basically this one's upstream, see main.setOther) + main.cancelOther(); + //now cancel main.main, and ensure that if this happens early an empty Subscription is passed down + main.cancelMainAndComplete(); } @Override @@ -117,7 +124,8 @@ return; } once = true; - main.onComplete(); + //cancel main.main, and ensure that if this happens early an empty Subscription is passed down + main.cancelMainAndComplete(); } } @@ -174,13 +182,23 @@ main.request(n); } - void cancelMain() { + void cancelMainAndComplete() { Subscription s = main; if (s != Operators.cancelledSubscription()) { s = MAIN.getAndSet(this, Operators.cancelledSubscription()); if (s != null && s != Operators.cancelledSubscription()) { s.cancel(); } + + if (s == null) { + // this indicates the Other completed early, even before `main` was set. + // let's pass an empty Subscription down and complete immediately + Operators.complete(actual); + } + else { + // if s wasn't null then Main Subscription was set and actual.onSubscribe already called + actual.onComplete(); + } } } @@ -196,7 +214,15 @@ @Override public void cancel() { - cancelMain(); + //similar to cancelMainAndComplete() but at this stage we only care about cancellation upstream + Subscription s = main; + if (s != Operators.cancelledSubscription()) { + s = MAIN.getAndSet(this, Operators.cancelledSubscription()); + if (s != null && s != Operators.cancelledSubscription()) { + s.cancel(); + } + } + //we do cancel the other subscriber too cancelOther(); } @@ -219,14 +245,13 @@ @Override public void onError(Throwable t) { - if (main == null) { if (MAIN.compareAndSet(this, null, Operators.cancelledSubscription())) { Operators.error(actual, t); return; } } - cancel(); + cancelOther(); actual.onError(t); } @@ -235,12 +260,11 @@ public void onComplete() { if (main == null) { if (MAIN.compareAndSet(this, null, Operators.cancelledSubscription())) { - cancelOther(); Operators.complete(actual); return; } } - cancel(); + cancelOther(); actual.onComplete(); } Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxTap.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxTap.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxTap.java (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -0,0 +1,359 @@ +/* + * Copyright (c) 2022-2023 VMware Inc. or its affiliates, All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reactor.core.publisher; + +import org.reactivestreams.Subscription; + +import reactor.core.CoreSubscriber; +import reactor.core.Exceptions; +import reactor.core.Fuseable.ConditionalSubscriber; +import reactor.core.observability.SignalListener; +import reactor.core.observability.SignalListenerFactory; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * A generic per-Subscription side effect {@link Flux} that notifies a {@link SignalListener} of most events. + * + * @author Simon Baslé + */ +final class FluxTap extends InternalFluxOperator { + + final SignalListenerFactory tapFactory; + final STATE commonTapState; + + FluxTap(Flux source, SignalListenerFactory tapFactory) { + super(source); + this.tapFactory = tapFactory; + this.commonTapState = tapFactory.initializePublisherState(source); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) throws Throwable { + //if the SignalListener cannot be created, all we can do is error the subscriber. + //after it is created, in case doFirst fails we can additionally try to invoke doFinally. + //note that if the later handler also fails, then that exception is thrown. + SignalListener signalListener; + try { + //TODO replace currentContext() with contextView() when available + signalListener = tapFactory.createListener(source, actual.currentContext().readOnly(), commonTapState); + } + catch (Throwable generatorError) { + Operators.error(actual, generatorError); + return null; + } + // Attempt to wrap the SignalListener with one that restores ThreadLocals from Context on each listener methods + // (only if ContextPropagation.isContextPropagationAvailable() is true) + signalListener = ContextPropagationSupport.isContextPropagationAvailable() ? + ContextPropagation.contextRestoreForTap(signalListener, actual::currentContext) : signalListener; + + try { + signalListener.doFirst(); + } + catch (Throwable listenerError) { + signalListener.handleListenerError(listenerError); + Operators.error(actual, listenerError); + return null; + } + + // Invoked AFTER doFirst + Context ctx; + try { + ctx = signalListener.addToContext(actual.currentContext()); + } + catch (Throwable e) { + IllegalStateException listenerError = new IllegalStateException( + "Unable to augment tap Context at subscription via addToContext", e); + signalListener.handleListenerError(listenerError); + Operators.error(actual, listenerError); + return null; + } + + if (actual instanceof ConditionalSubscriber) { + //noinspection unchecked + return new TapConditionalSubscriber<>((ConditionalSubscriber) actual, signalListener, ctx); + } + return new TapSubscriber<>(actual, signalListener, ctx); + } + + @Nullable + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return super.scanUnsafe(key); + } + + //TODO support onErrorContinue around listener errors + static class TapSubscriber implements InnerOperator { + + final CoreSubscriber actual; + final Context context; + final SignalListener listener; + + boolean done; + Subscription s; + + TapSubscriber(CoreSubscriber actual, + SignalListener signalListener, Context ctx) { + this.actual = actual; + this.listener = signalListener; + this.context = ctx; + } + + @Override + public CoreSubscriber actual() { + return this.actual; + } + + @Override + public Context currentContext() { + return this.context; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.TERMINATED) return done; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + /** + * Cancel the prepared subscription, pass the listener error to {@link SignalListener#handleListenerError(Throwable)} + * and then terminate the downstream directly with same error (without invoking any other handler). + * + * @param listenerError the exception thrown from a handler method before the subscription was set + * @param toCancel the {@link Subscription} that was prepared but not sent downstream + */ + protected void handleListenerErrorPreSubscription(Throwable listenerError, Subscription toCancel) { + toCancel.cancel(); + listener.handleListenerError(listenerError); + Operators.error(actual, listenerError); + } + + /** + * Cancel the active subscription, pass the listener error to {@link SignalListener#handleListenerError(Throwable)} + * and then terminate the downstream directly with same error (without invoking any other handler). + * + * @param listenerError the exception thrown from a handler method + */ + protected void handleListenerErrorAndTerminate(Throwable listenerError) { + s.cancel(); + listener.handleListenerError(listenerError); + actual.onError(listenerError); //TODO wrap ? hooks ? + } + + /** + * Cancel the active subscription, pass the listener error to {@link SignalListener#handleListenerError(Throwable)}, + * combine it with the original error and then terminate the downstream directly this combined exception + * (without invoking any other handler). + * + * @param listenerError the exception thrown from a handler method + * @param originalError the exception that was about to occur when handler was invoked + */ + protected void handleListenerErrorMultipleAndTerminate(Throwable listenerError, Throwable originalError) { + s.cancel(); + listener.handleListenerError(listenerError); + RuntimeException multiple = Exceptions.multiple(listenerError, originalError); + actual.onError(multiple); //TODO wrap ? hooks ? + } + + /** + * After the downstream is considered terminated (or cancelled), pass the listener error to + * {@link SignalListener#handleListenerError(Throwable)} then drop that error. + * + * @param listenerError the exception thrown from a handler method happening after sequence termination + */ + protected void handleListenerErrorPostTermination(Throwable listenerError) { + listener.handleListenerError(listenerError); + Operators.onErrorDropped(listenerError, actual.currentContext()); + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + + try { + listener.doOnSubscription(); + } + catch (Throwable observerError) { + handleListenerErrorPreSubscription(observerError, s); + return; + } + actual.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (done) { + try { + listener.doOnMalformedOnNext(t); + } + catch (Throwable observerError) { + handleListenerErrorPostTermination(observerError); + } + finally { + Operators.onNextDropped(t, currentContext()); + } + return; + } + try { + listener.doOnNext(t); + } + catch (Throwable observerError) { + handleListenerErrorAndTerminate(observerError); + return; + } + actual.onNext(t); + } + + @Override + public void onError(Throwable t) { + if (done) { + try { + listener.doOnMalformedOnError(t); + } + catch (Throwable observerError) { + handleListenerErrorPostTermination(observerError); + } + finally { + Operators.onErrorDropped(t, currentContext()); + } + return; + } + done = true; + + try { + listener.doOnError(t); + } + catch (Throwable observerError) { + //any error in the hooks interrupts other hooks, including doFinally + handleListenerErrorMultipleAndTerminate(observerError, t); + return; + } + + actual.onError(t); //RS: onError MUST terminate normally and not throw + + try { + listener.doAfterError(t); + listener.doFinally(SignalType.ON_ERROR); + } + catch (Throwable observerError) { + handleListenerErrorPostTermination(observerError); + } + } + + @Override + public void onComplete() { + if (done) { + try { + listener.doOnMalformedOnComplete(); + } + catch (Throwable observerError) { + handleListenerErrorPostTermination(observerError); + } + return; + } + done = true; + + try { + listener.doOnComplete(); + } + catch (Throwable observerError) { + handleListenerErrorAndTerminate(observerError); + return; + } + + actual.onComplete(); //RS: onComplete MUST terminate normally and not throw + + try { + listener.doAfterComplete(); + listener.doFinally(SignalType.ON_COMPLETE); + } + catch (Throwable observerError) { + handleListenerErrorPostTermination(observerError); + } + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + try { + listener.doOnRequest(n); + } + catch (Throwable observerError) { + handleListenerErrorAndTerminate(observerError); + return; + } + s.request(n); + } + } + + @Override + public void cancel() { + try { + listener.doOnCancel(); + } + catch (Throwable observerError) { + handleListenerErrorAndTerminate(observerError); + return; + } + + try { + s.cancel(); + } + finally { + try { + listener.doFinally(SignalType.CANCEL); + } + catch (Throwable observerError) { + handleListenerErrorAndTerminate(observerError); //redundant s.cancel + } + } + } + } + + static final class TapConditionalSubscriber extends TapSubscriber implements ConditionalSubscriber { + + final ConditionalSubscriber actualConditional; + + public TapConditionalSubscriber(ConditionalSubscriber actual, + SignalListener signalListener, Context ctx) { + super(actual, signalListener, ctx); + this.actualConditional = actual; + } + + @Override + public boolean tryOnNext(T t) { + if (actualConditional.tryOnNext(t)) { + try { + listener.doOnNext(t); + } + catch (Throwable listenerError) { + handleListenerErrorAndTerminate(listenerError); + } + return true; + } + return false; + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxTapFuseable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxTapFuseable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxTapFuseable.java (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -0,0 +1,308 @@ +/* + * Copyright (c) 2022-2023 VMware Inc. or its affiliates, All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reactor.core.publisher; + +import org.reactivestreams.Subscription; + +import reactor.core.CoreSubscriber; +import reactor.core.Exceptions; +import reactor.core.Fuseable; +import reactor.core.observability.SignalListener; +import reactor.core.observability.SignalListenerFactory; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * A {@link Fuseable} generic per-Subscription side effect {@link Flux} that notifies a + * {@link SignalListener} of most events. + * + * @author Simon Baslé + */ +final class FluxTapFuseable extends InternalFluxOperator implements Fuseable { + + final SignalListenerFactory tapFactory; + final STATE commonTapState; + + FluxTapFuseable(Flux source, SignalListenerFactory tapFactory) { + super(source); + this.tapFactory = tapFactory; + this.commonTapState = tapFactory.initializePublisherState(source); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) throws Throwable { + //if the SignalListener cannot be created, all we can do is error the subscriber. + //after it is created, in case doFirst fails we can additionally try to invoke doFinally. + //note that if the later handler also fails, then that exception is thrown. + SignalListener signalListener; + try { + //TODO replace currentContext() with contextView() when available + signalListener = tapFactory.createListener(source, actual.currentContext().readOnly(), commonTapState); + } + catch (Throwable generatorError) { + Operators.error(actual, generatorError); + return null; + } + // Attempt to wrap the SignalListener with one that restores ThreadLocals from Context on each listener methods + // (only if ContextPropagation.isContextPropagationAvailable() is true) + signalListener = ContextPropagationSupport.isContextPropagationAvailable() ? + ContextPropagation.contextRestoreForTap(signalListener, actual::currentContext) : signalListener; + + try { + signalListener.doFirst(); + } + catch (Throwable listenerError) { + signalListener.handleListenerError(listenerError); + Operators.error(actual, listenerError); + return null; + } + + // Invoked AFTER doFirst + Context ctx; + try { + ctx = signalListener.addToContext(actual.currentContext()); + } + catch (Throwable e) { + IllegalStateException listenerError = new IllegalStateException( + "Unable to augment tap Context at subscription via addToContext", e); + signalListener.handleListenerError(listenerError); + Operators.error(actual, listenerError); + return null; + } + + if (actual instanceof ConditionalSubscriber) { + //noinspection unchecked + return new TapConditionalFuseableSubscriber<>( + (ConditionalSubscriber) actual, signalListener, ctx); + } + return new TapFuseableSubscriber<>(actual, signalListener, ctx); + } + + @Nullable + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return super.scanUnsafe(key); + } + + //TODO support onErrorContinue around listener errors + static class TapFuseableSubscriber extends FluxTap.TapSubscriber implements QueueSubscription { + + int mode; + QueueSubscription qs; + + TapFuseableSubscriber(CoreSubscriber actual, + SignalListener signalListener, Context ctx) { + super(actual, signalListener, ctx); + } + + /** + * Cancel the active subscription, pass the listener error to {@link SignalListener#handleListenerError(Throwable)} + * then return that same exception wrapped via {@link Exceptions#propagate(Throwable)} if needed. + * It should be immediately thrown to terminate the downstream directly from {@link #poll()} (without invoking + * any other handler). + * + * @param listenerError the exception thrown from a handler method + */ + protected RuntimeException handleObserverErrorInPoll(Throwable listenerError) { + qs.cancel(); + listener.handleListenerError(listenerError); + return Exceptions.propagate(listenerError); + } + + /** + * Cancel the active subscription, pass the listener error to {@link SignalListener#handleListenerError(Throwable)}, + * combine it with the original error and then return the combined exception. The returned exception should be + * immediately thrown to terminate the downstream directly from {@link #poll()} (without invoking any other handler). + * + * @param listenerError the exception thrown from a handler method + * @param pollError the exception that was about to be thrown from poll when handler was invoked + */ + protected RuntimeException handleObserverErrorMultipleInPoll(Throwable listenerError, RuntimeException pollError) { + qs.cancel(); + listener.handleListenerError(listenerError); + return Exceptions.multiple(listenerError, pollError); + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + if (!(s instanceof QueueSubscription)) { + handleListenerErrorPreSubscription(new IllegalStateException("Fuseable subscriber but no QueueSubscription"), s); + return; + } + this.s = s; + //noinspection unchecked + this.qs = (QueueSubscription) s; + + try { + listener.doOnSubscription(); + } + catch (Throwable listenerError) { + handleListenerErrorPreSubscription(listenerError, s); + return; + } + actual.onSubscribe(this); //should trigger requestFusion + } + } + + @Override + public int requestFusion(int requestedMode) { + if (qs == null) { + this.mode = NONE; + return NONE; + } + this.mode = qs.requestFusion(requestedMode); + + try { + listener.doOnFusion(this.mode); + return mode; + } + catch (Throwable listenerError) { + if (mode == ASYNC || mode == NONE) { + handleListenerErrorAndTerminate(listenerError); + return NONE; + } + //for SYNC, no interruption + listener.handleListenerError(listenerError); + return mode; + } + } + + @SuppressWarnings("ConstantConditions") + @Override + public void onNext(T t) { + if (this.mode == ASYNC) { + actual.onNext(null); + return; //will observe onNext events through the lens of poll() + } + super.onNext(t); + } + + @Override + @Nullable + public T poll() { + if (qs == null) { + return null; + } + T v; + //try to poll. failure means doOnError. doOnError failure is combined with original + try { + v = qs.poll(); + } + catch (RuntimeException pollError) { + try { + listener.doOnError(pollError); + } + catch (Throwable listenerError) { + throw handleObserverErrorMultipleInPoll(listenerError, pollError); + } + + //the subscription can be considered cancelled at this point + //exceptionally we invoked doFinally _before_ the propagation (since it is throwing) + try { + listener.doFinally(SignalType.ON_ERROR); + } + catch (Throwable listenerError) { + throw handleObserverErrorMultipleInPoll(listenerError, pollError); + } + throw pollError; + } + + //SYNC fusion uses null as onComplete and throws as onError + //ASYNC fusion uses classic methods + if (v == null && (this.done || mode == SYNC)) { + try { + listener.doOnComplete(); + } + catch (Throwable listenerError) { + throw handleObserverErrorInPoll(listenerError); + } + + //exceptionally doFinally will be invoked before the downstream is notified of completion (return null) + try { + listener.doFinally(SignalType.ON_COMPLETE); + } + catch (Throwable listenerError) { + throw handleObserverErrorInPoll(listenerError); + } + + //notify the downstream of completion + return null; + } + + if (v != null) { + //this is an onNext event + try { + listener.doOnNext(v); + } + catch (Throwable listenerError) { + if (mode == SYNC) { + throw handleObserverErrorInPoll(listenerError); + } + handleListenerErrorAndTerminate(listenerError); + //TODO discard the element ? + return null; + } + } + return v; + } + + @Override + public int size() { + return qs == null ? 0 : qs.size(); + } + + @Override + public boolean isEmpty() { + return qs == null || qs.isEmpty(); + } + + @Override + public void clear() { + if (qs != null) { + qs.clear(); + } + } + } + + static final class TapConditionalFuseableSubscriber extends TapFuseableSubscriber implements ConditionalSubscriber { + + final ConditionalSubscriber actualConditional; + + public TapConditionalFuseableSubscriber(ConditionalSubscriber actual, + SignalListener signalListener, Context ctx) { + super(actual, signalListener, ctx); + this.actualConditional = actual; + } + + @Override + public boolean tryOnNext(T t) { + if (actualConditional.tryOnNext(t)) { + try { + listener.doOnNext(t); + } + catch (Throwable listenerError) { + handleListenerErrorAndTerminate(listenerError); + } + return true; + } + return false; + } + } +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxTapRestoringThreadLocals.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxTapRestoringThreadLocals.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxTapRestoringThreadLocals.java (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -0,0 +1,365 @@ +/* + * Copyright (c) 2022-2023 VMware Inc. or its affiliates, All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reactor.core.publisher; + +import io.micrometer.context.ContextSnapshot; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Exceptions; +import reactor.core.Fuseable.ConditionalSubscriber; +import reactor.core.observability.SignalListener; +import reactor.core.observability.SignalListenerFactory; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * A generic per-Subscription side effect {@link Flux} that notifies a {@link SignalListener} of most events. + * + * @author Simon Baslé + */ +final class FluxTapRestoringThreadLocals extends FluxOperator { + + final SignalListenerFactory tapFactory; + final STATE commonTapState; + + FluxTapRestoringThreadLocals(Flux source, SignalListenerFactory tapFactory) { + super(source); + this.tapFactory = tapFactory; + this.commonTapState = tapFactory.initializePublisherState(source); + } + + @Override + public void subscribe(CoreSubscriber actual) { + //if the SignalListener cannot be created, all we can do is error the subscriber. + //after it is created, in case doFirst fails we can additionally try to invoke doFinally. + //note that if the later handler also fails, then that exception is thrown. + SignalListener signalListener; + try { + //TODO replace currentContext() with contextView() when available + signalListener = tapFactory.createListener(source, actual.currentContext().readOnly(), commonTapState); + } + catch (Throwable generatorError) { + Operators.error(actual, generatorError); + return; + } + + try { + signalListener.doFirst(); + } + catch (Throwable listenerError) { + signalListener.handleListenerError(listenerError); + Operators.error(actual, listenerError); + return; + } + + // invoked AFTER doFirst + Context alteredContext; + try { + alteredContext = signalListener.addToContext(actual.currentContext()); + } + catch (Throwable e) { + IllegalStateException listenerError = new IllegalStateException( + "Unable to augment tap Context at subscription via addToContext", e); + signalListener.handleListenerError(listenerError); + Operators.error(actual, listenerError); + return; + } + + try (ContextSnapshot.Scope ignored = ContextPropagation.setThreadLocals(alteredContext)) { + source.subscribe(new TapSubscriber<>(actual, signalListener, alteredContext)); + } + } + + @Nullable + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return super.scanUnsafe(key); + } + + //TODO support onErrorContinue around listener errors + static class TapSubscriber implements ConditionalSubscriber, InnerOperator { + + final CoreSubscriber actual; + final ConditionalSubscriber actualConditional; + final Context context; + final SignalListener listener; + + boolean done; + Subscription s; + + TapSubscriber(CoreSubscriber actual, SignalListener signalListener, Context ctx) { + this.actual = actual; + this.listener = signalListener; + this.context = ctx; + if (actual instanceof ConditionalSubscriber) { + this.actualConditional = (ConditionalSubscriber) actual; + } else { + this.actualConditional = null; + } + } + + @Override + public CoreSubscriber actual() { + return this.actual; + } + + @Override + public Context currentContext() { + return this.context; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.TERMINATED) return done; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + /** + * Cancel the prepared subscription, pass the listener error to {@link SignalListener#handleListenerError(Throwable)} + * and then terminate the downstream directly with same error (without invoking any other handler). + * + * @param listenerError the exception thrown from a handler method before the subscription was set + * @param toCancel the {@link Subscription} that was prepared but not sent downstream + */ + protected void handleListenerErrorPreSubscription(Throwable listenerError, Subscription toCancel) { + toCancel.cancel(); + listener.handleListenerError(listenerError); + try (ContextSnapshot.Scope ignored = + ContextPropagation.setThreadLocals(actual.currentContext())) { + Operators.error(actual, listenerError); + } + } + + /** + * Cancel the active subscription, pass the listener error to {@link SignalListener#handleListenerError(Throwable)} + * and then terminate the downstream directly with same error (without invoking any other handler). + * + * @param listenerError the exception thrown from a handler method + */ + protected void handleListenerErrorAndTerminate(Throwable listenerError) { + s.cancel(); + listener.handleListenerError(listenerError); + try (ContextSnapshot.Scope ignored = + ContextPropagation.setThreadLocals(actual.currentContext())) { + actual.onError(listenerError); //TODO hooks ? + } + } + + /** + * Cancel the active subscription, pass the listener error to {@link SignalListener#handleListenerError(Throwable)}, + * combine it with the original error and then terminate the downstream directly this combined exception + * (without invoking any other handler). + * + * @param listenerError the exception thrown from a handler method + * @param originalError the exception that was about to occur when handler was invoked + */ + protected void handleListenerErrorMultipleAndTerminate(Throwable listenerError, Throwable originalError) { + s.cancel(); + listener.handleListenerError(listenerError); + RuntimeException multiple = Exceptions.multiple(listenerError, originalError); + try (ContextSnapshot.Scope ignored = + ContextPropagation.setThreadLocals(actual.currentContext())) { + actual.onError(multiple); //TODO hooks ? + } + } + + /** + * After the downstream is considered terminated (or cancelled), pass the listener error to + * {@link SignalListener#handleListenerError(Throwable)} then drop that error. + * + * @param listenerError the exception thrown from a handler method happening after sequence termination + */ + protected void handleListenerErrorPostTermination(Throwable listenerError) { + listener.handleListenerError(listenerError); + Operators.onErrorDropped(listenerError, actual.currentContext()); + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + + try { + listener.doOnSubscription(); + } catch (Throwable observerError) { + handleListenerErrorPreSubscription(observerError, s); + return; + } + try (ContextSnapshot.Scope ignored = + ContextPropagation.setThreadLocals(actual.currentContext())) { + actual.onSubscribe(this); + } + } + } + + @Override + public void onNext(T t) { + if (done) { + try { + listener.doOnMalformedOnNext(t); + } catch (Throwable observerError) { + handleListenerErrorPostTermination(observerError); + } finally { + Operators.onNextDropped(t, currentContext()); + } + return; + } + try { + listener.doOnNext(t); + } catch (Throwable observerError) { + handleListenerErrorAndTerminate(observerError); + return; + } + try (ContextSnapshot.Scope ignored = + ContextPropagation.setThreadLocals(actual.currentContext())) { + actual.onNext(t); + } + } + + @Override + public boolean tryOnNext(T t) { + try (ContextSnapshot.Scope ignored = + ContextPropagation.setThreadLocals(actual.currentContext())) { + if (actualConditional != null) { + if (!actualConditional.tryOnNext(t)) { + return false; + } + } else { + actual.onNext(t); + } + } + try { + listener.doOnNext(t); + } catch (Throwable listenerError) { + handleListenerErrorAndTerminate(listenerError); + } + return true; + } + + @Override + public void onError(Throwable t) { + if (done) { + try { + listener.doOnMalformedOnError(t); + } catch (Throwable observerError) { + handleListenerErrorPostTermination(observerError); + } finally { + Operators.onErrorDropped(t, currentContext()); + } + return; + } + done = true; + + try { + listener.doOnError(t); + } catch (Throwable observerError) { + //any error in the hooks interrupts other hooks, including doFinally + handleListenerErrorMultipleAndTerminate(observerError, t); + return; + } + + try (ContextSnapshot.Scope ignored = + ContextPropagation.setThreadLocals(actual.currentContext())) { + actual.onError(t); //RS: onError MUST terminate normally and not throw + } + + try { + listener.doAfterError(t); + listener.doFinally(SignalType.ON_ERROR); + } catch (Throwable observerError) { + handleListenerErrorPostTermination(observerError); + } + } + + @Override + public void onComplete() { + if (done) { + try { + listener.doOnMalformedOnComplete(); + } catch (Throwable observerError) { + handleListenerErrorPostTermination(observerError); + } + return; + } + done = true; + + try { + listener.doOnComplete(); + } catch (Throwable observerError) { + handleListenerErrorAndTerminate(observerError); + return; + } + + try (ContextSnapshot.Scope ignored = + ContextPropagation.setThreadLocals(actual.currentContext())) { + actual.onComplete(); //RS: onComplete MUST terminate normally and not throw + } + + try { + listener.doAfterComplete(); + listener.doFinally(SignalType.ON_COMPLETE); + } catch (Throwable observerError) { + handleListenerErrorPostTermination(observerError); + } + } + + @Override + public void request(long n) { + try (ContextSnapshot.Scope ignored = + ContextPropagation.setThreadLocals(this.context)) { + if (Operators.validate(n)) { + try { + listener.doOnRequest(n); + } catch (Throwable observerError) { + handleListenerErrorAndTerminate(observerError); + return; + } + s.request(n); + } + } + } + + @Override + public void cancel() { + try (ContextSnapshot.Scope ignored = + ContextPropagation.setThreadLocals(this.context)) { + try { + listener.doOnCancel(); + } catch (Throwable observerError) { + handleListenerErrorAndTerminate(observerError); + return; + } + + try { + s.cancel(); + } finally { + try { + listener.doFinally(SignalType.CANCEL); + } catch (Throwable observerError) { + handleListenerErrorAndTerminate(observerError); //redundant s.cancel + } + } + } + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxUsingWhen.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxUsingWhen.java (.../FluxUsingWhen.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxUsingWhen.java (.../FluxUsingWhen.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2018-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,7 @@ import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; + import reactor.core.CoreSubscriber; import reactor.core.Exceptions; import reactor.core.Fuseable.ConditionalSubscriber; @@ -167,8 +168,6 @@ Subscription resourceSubscription; boolean resourceProvided; - UsingWhenSubscriber closureSubscriber; - ResourceSubscriber(CoreSubscriber actual, Function> resourceClosure, Function> asyncComplete, @@ -192,15 +191,14 @@ resourceProvided = true; final Publisher p = deriveFluxFromResource(resource, resourceClosure); - this.closureSubscriber = prepareSubscriberForResource(resource, + + p.subscribe(FluxUsingWhen.prepareSubscriberForResource(resource, this.actual, this.asyncComplete, this.asyncError, this.asyncCancel, - this); + this)); - p.subscribe(closureSubscriber); - if (!isMonoSource) { resourceSubscription.cancel(); } @@ -245,14 +243,9 @@ public void cancel() { if (!resourceProvided) { resourceSubscription.cancel(); - super.cancel(); } - else { - super.terminate(); - if (closureSubscriber != null) { - closureSubscriber.cancel(); - } - } + + super.cancel(); } @Override @@ -272,12 +265,6 @@ //state that differs in the different variants final CoreSubscriber actual; - - volatile Subscription s; - static final AtomicReferenceFieldUpdaterSUBSCRIPTION = - AtomicReferenceFieldUpdater.newUpdater(UsingWhenSubscriber.class, - Subscription.class, "s"); - //rest of the state is always the same final S resource; final Function> asyncComplete; @@ -294,6 +281,7 @@ * Also stores the onComplete terminal state as {@link Exceptions#TERMINATED} */ Throwable error; + Subscription s; UsingWhenSubscriber(CoreSubscriber actual, S resource, @@ -318,7 +306,7 @@ public Object scanUnsafe(Attr key) { if (key == Attr.TERMINATED) return error != null; if (key == Attr.ERROR) return (error == Exceptions.TERMINATED) ? null : error; - if (key == Attr.CANCELLED) return s == Operators.cancelledSubscription(); + if (key == Attr.CANCELLED) return callbackApplied == 3; if (key == Attr.PARENT) return s; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; @@ -334,22 +322,21 @@ @Override public void cancel() { - if (CALLBACK_APPLIED.compareAndSet(this, 0, 1)) { - if (Operators.terminate(SUBSCRIPTION, this)) { - try { - if (asyncCancel != null) { - Flux.from(asyncCancel.apply(resource)) - .subscribe(new CancelInner(this)); - } - else { - Flux.from(asyncComplete.apply(resource)) - .subscribe(new CancelInner(this)); - } + if (CALLBACK_APPLIED.compareAndSet(this, 0, 3)) { + this.s.cancel(); + try { + if (asyncCancel != null) { + Flux.from(asyncCancel.apply(resource)) + .subscribe(new CancelInner(this)); } - catch (Throwable error) { - Loggers.getLogger(FluxUsingWhen.class).warn("Error generating async resource cleanup during onCancel", error); + else { + Flux.from(asyncComplete.apply(resource)) + .subscribe(new CancelInner(this)); } } + catch (Throwable error) { + Loggers.getLogger(FluxUsingWhen.class).warn("Error generating async resource cleanup during onCancel", error); + } } } @@ -360,7 +347,7 @@ @Override public void onError(Throwable t) { - if (CALLBACK_APPLIED.compareAndSet(this, 0, 1)) { + if (CALLBACK_APPLIED.compareAndSet(this, 0, 2)) { Publisher p; try { @@ -419,7 +406,7 @@ actual.onSubscribe(this); } else { - arbiter.set(s); + arbiter.set(this); } } } Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxWindow.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxWindow.java (.../FluxWindow.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxWindow.java (.../FluxWindow.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,6 +33,8 @@ import reactor.util.annotation.Nullable; import reactor.util.context.Context; +import static reactor.core.Exceptions.wrapSource; + /** * Splits the source sequence into possibly overlapping publishers. * @@ -195,7 +197,7 @@ Sinks.Many w = window; if (w != null) { window = null; - w.emitError(t, Sinks.EmitFailureHandler.FAIL_FAST); + w.emitError(wrapSource(t), Sinks.EmitFailureHandler.FAIL_FAST); } actual.onError(t); @@ -376,7 +378,7 @@ Sinks.Many w = window; if (w != null) { window = null; - w.emitError(t, Sinks.EmitFailureHandler.FAIL_FAST); + w.emitError(wrapSource(t), Sinks.EmitFailureHandler.FAIL_FAST); } actual.onError(t); @@ -586,7 +588,7 @@ done = true; for (Sinks.Many w : this) { - w.emitError(t, Sinks.EmitFailureHandler.FAIL_FAST); + w.emitError(wrapSource(t), Sinks.EmitFailureHandler.FAIL_FAST); } clear(); Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxWindowBoundary.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxWindowBoundary.java (.../FluxWindowBoundary.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxWindowBoundary.java (.../FluxWindowBoundary.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,6 +36,8 @@ import reactor.util.concurrent.Queues; import reactor.util.context.Context; +import static reactor.core.Exceptions.wrapSource; + /** * Splits the source sequence into continuous, non-overlapping windowEnds * where the window boundary is signalled by another Publisher @@ -294,7 +296,8 @@ q.clear(); Throwable e = Exceptions.terminate(ERROR, this); if (e != Exceptions.TERMINATED) { - w.emitError(e, Sinks.EmitFailureHandler.FAIL_FAST); + w.emitError(wrapSource(e), + Sinks.EmitFailureHandler.FAIL_FAST); a.onError(e); } Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxWindowPredicate.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxWindowPredicate.java (.../FluxWindowPredicate.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxWindowPredicate.java (.../FluxWindowPredicate.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,6 +36,8 @@ import reactor.util.annotation.Nullable; import reactor.util.context.Context; +import static reactor.core.Exceptions.wrapSource; + /** * Cut a sequence into non-overlapping windows where each window boundary is determined by * a {@link Predicate} on the values. The predicate can be used in several modes: @@ -109,7 +111,7 @@ } static final class WindowPredicateMain - implements Fuseable.QueueSubscription>, + implements QueueSubscription>, InnerOperator> { final CoreSubscriber> actual; @@ -347,7 +349,7 @@ windowCount = 0; WindowFlux g = window; if (g != null) { - g.onError(e); + g.onError(wrapSource(e)); } actual.onError(e); window = null; @@ -575,7 +577,7 @@ } static final class WindowFlux extends Flux - implements Fuseable, Fuseable.QueueSubscription, InnerOperator { + implements Fuseable, QueueSubscription, InnerOperator { final Queue queue; @@ -920,4 +922,4 @@ } } -} +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxWindowTimeout.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxWindowTimeout.java (.../FluxWindowTimeout.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxWindowTimeout.java (.../FluxWindowTimeout.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2017-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,33 +26,43 @@ import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; - import reactor.core.CoreSubscriber; import reactor.core.Disposable; import reactor.core.Disposables; import reactor.core.Exceptions; import reactor.core.Scannable; import reactor.core.scheduler.Scheduler; +import reactor.util.annotation.Nullable; import reactor.util.concurrent.Queues; +import reactor.util.context.Context; +import static reactor.core.Exceptions.wrapSource; + /** * @author David Karnok */ final class FluxWindowTimeout extends InternalFluxOperator> { - final int maxSize; - final long timespan; - final TimeUnit unit; - final Scheduler timer; + final int maxSize; + final long timespan; + final TimeUnit unit; + final Scheduler timer; + final boolean fairBackpressure; - FluxWindowTimeout(Flux source, int maxSize, long timespan, TimeUnit unit, Scheduler timer) { + FluxWindowTimeout(Flux source, + int maxSize, + long timespan, + TimeUnit unit, + Scheduler timer, + boolean fairBackpressure) { super(source); if (timespan <= 0) { throw new IllegalArgumentException("Timeout period must be strictly positive"); } if (maxSize <= 0) { throw new IllegalArgumentException("maxSize must be strictly positive"); } + this.fairBackpressure = fairBackpressure; this.timer = Objects.requireNonNull(timer, "Timer"); this.timespan = timespan; this.unit = Objects.requireNonNull(unit, "unit"); @@ -61,19 +71,1638 @@ @Override public CoreSubscriber subscribeOrReturn(CoreSubscriber> actual) { - return new WindowTimeoutSubscriber<>(actual, maxSize, - timespan, unit, - timer); + if (fairBackpressure) { + return new WindowTimeoutWithBackpressureSubscriber<>(actual, maxSize, timespan, unit, timer, null); + } + return new WindowTimeoutSubscriber<>(actual, maxSize, timespan, unit, timer); } @Override public Object scanUnsafe(Attr key) { - if (key == Attr.RUN_ON) return timer; - if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; + if (key == Attr.RUN_ON) { + return timer; + } + if (key == Attr.RUN_STYLE) { + return Attr.RunStyle.ASYNC; + } return super.scanUnsafe(key); } + static final class WindowTimeoutWithBackpressureSubscriber + implements InnerOperator> { + + @Nullable + final StateLogger logger; + + final CoreSubscriber> actual; + final long timespan; + final TimeUnit unit; + final Scheduler scheduler; + final int maxSize; + final Scheduler.Worker worker; + final int limit; + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(WindowTimeoutWithBackpressureSubscriber.class, "requested"); + + + volatile long state; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater STATE = + AtomicLongFieldUpdater.newUpdater(WindowTimeoutWithBackpressureSubscriber.class, "state"); + + static final long CANCELLED_FLAG = + 0b1000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000L; + static final long TERMINATED_FLAG = + 0b0100_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000L; + static final long HAS_UNSENT_WINDOW = + 0b0010_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000L; + static final long HAS_WORK_IN_PROGRESS = + 0b0001_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000L; + static final long REQUEST_INDEX_MASK = + 0b0000_1111_1111_1111_1111_1111_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000L; + static final long ACTIVE_WINDOW_INDEX_MASK = + 0b0000_0000_0000_0000_0000_0000_1111_1111_1111_1111_1111_0000_0000_0000_0000_0000L; + static final long NEXT_WINDOW_INDEX_MASK = + 0b0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_1111_1111_1111_1111_1111L; + + static final int ACTIVE_WINDOW_INDEX_SHIFT = 20; + static final int REQUEST_INDEX_SHIFT = 40; + + boolean done; + Throwable error; + + Subscription s; + + InnerWindow window; + + WindowTimeoutWithBackpressureSubscriber(CoreSubscriber> actual, + int maxSize, + long timespan, + TimeUnit unit, + Scheduler scheduler, + @Nullable StateLogger logger) { + this.actual = actual; + this.timespan = timespan; + this.unit = unit; + this.scheduler = scheduler; + this.maxSize = maxSize; + this.limit = Operators.unboundedOrLimit(maxSize); + this.worker = scheduler.createWorker(); + this.logger = logger; + + STATE.lazySet(this, 1); + } + + @Override + public CoreSubscriber> actual() { + return this.actual; + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + + this.actual.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (this.done) { + Operators.onNextDropped(t, this.actual.currentContext()); + return; + } + + while(true) { + if (isCancelled(this.state)) { + Operators.onDiscard(t, this.actual.currentContext()); + return; + } + + final InnerWindow window = this.window; + if (window.sendNext(t)) { + return; + } + } + } + + @Override + public void onError(Throwable t) { + if (this.done) { + Operators.onErrorDropped(t, this.actual.currentContext()); + return; + } + + this.error = t; + this.done = true; + + final long previousState = markTerminated(this); + + if (isCancelled(previousState) || isTerminated(previousState)) { + return; + } + + final InnerWindow window = this.window; + if (window != null) { + window.sendError(wrapSource(t)); + + if (hasUnsentWindow(previousState)) { + return; + } + } + + if (hasWorkInProgress(previousState)) { + return; + } + + this.actual.onError(t); + } + + @Override + public void onComplete() { + if (this.done) { + return; + } + + this.done = true; + + + final long previousState = markTerminated(this); + + if (isCancelled(previousState) || isTerminated(previousState)) { + return; + } + + InnerWindow window = this.window; + if (window != null) { + window.sendComplete(); + + if (hasUnsentWindow(previousState)) { + return; + } + } + + if (hasWorkInProgress(previousState)) { + return; + } + + this.actual.onComplete(); + } + + @Override + public void request(long n) { + final long previousRequested = Operators.addCap(REQUESTED, this, n); + if (previousRequested == Long.MAX_VALUE) { + return; + } + + long previousState; + long expectedState; + boolean hasUnsentWindow; + for (;;) { + previousState = this.state; + + if (isCancelled(previousState)) { + return; + } + + if (hasWorkInProgress(previousState)) { + long nextState = (previousState & ~REQUEST_INDEX_MASK) | incrementRequestIndex(previousState); + if (STATE.compareAndSet(this, previousState, nextState)) { + if (this.logger != null) { + this.logger.log(this.toString(), "mre", previousState, nextState); + } + return; + } + + continue; + } + + hasUnsentWindow = hasUnsentWindow(previousState); + + if (!hasUnsentWindow && (isTerminated(previousState) || activeWindowIndex(previousState) == nextWindowIndex(previousState))) { + return; + } + + expectedState = + (previousState &~ HAS_UNSENT_WINDOW) | + HAS_WORK_IN_PROGRESS; + if (STATE.compareAndSet(this, previousState, expectedState)) { + if (this.logger != null) { + this.logger.log(this.toString(), "mre", previousState, expectedState); + } + break; + } + } + + drain(previousState, expectedState); + } + + void tryCreateNextWindow(int windowIndex) { + long previousState; + long expectedState; + + for (;;) { + previousState = this.state; + + if (isCancelled(previousState)) { + return; + } + + if (nextWindowIndex(previousState) != windowIndex) { + return; + } + + boolean hasWorkInProgress = hasWorkInProgress(previousState); + if (!hasWorkInProgress && isTerminated(previousState) && !hasUnsentWindow(previousState)) { + return; + } + + expectedState = (previousState &~ NEXT_WINDOW_INDEX_MASK) | incrementNextWindowIndex(previousState) | HAS_WORK_IN_PROGRESS; + if (STATE.compareAndSet(this, previousState, expectedState)) { + + if (hasWorkInProgress) { + return; + } + + break; + } + } + + drain(previousState, expectedState); + } + + void drain(long previousState, long expectedState) { + for (;;) { + long n = this.requested; + if (this.logger != null) { + this.logger.log(this.toString(), "dr"+n, previousState, + expectedState); + } + + final boolean hasUnsentWindow = hasUnsentWindow(previousState); + + final int activeWindowIndex = activeWindowIndex(expectedState); + final int nextWindowIndex = nextWindowIndex(expectedState); + // short path to exit from loop if there is active window which was not + // terminated yet and this window is delivered so we dont have to + // perform any work (we just need to remove lock and exit) + if (activeWindowIndex == nextWindowIndex && !hasUnsentWindow) { + expectedState = markWorkDone(this, expectedState); + previousState = expectedState | HAS_WORK_IN_PROGRESS; + + if (isCancelled(expectedState)) { + return; + } + + if (isTerminated(expectedState)) { + final Throwable e = this.error; + if (e != null) { + this.actual.onError(e); + } + else { + this.actual.onComplete(); + } + return; + } + + + if (!hasWorkInProgress(expectedState)) { + return; + } + + continue; + } + + if (n > 0) { + // here we are if we entered from the request() method + if (hasUnsentWindow) { + final InnerWindow currentUnsentWindow = this.window; + + // Delivers current unsent window + this.actual.onNext(currentUnsentWindow); + + if (n != Long.MAX_VALUE) { + n = REQUESTED.decrementAndGet(this); + if (this.logger != null) { + this.logger.log(this.toString(), "dec", n, n); + } + } + + // Marks as sent current unsent window. Also, delivers + // onComplete to the window subscriber if it was not delivered + final long previousInnerWindowState = currentUnsentWindow.sendSent(); + + if (isTerminated(expectedState)) { + Throwable e = this.error; + if (e != null) { + if (!isTerminated(previousInnerWindowState)) { + currentUnsentWindow.sendError(e); + } + this.actual.onError(e); + } + else { + if (!isTerminated(previousInnerWindowState)) { + currentUnsentWindow.sendComplete(); + } + this.actual.onComplete(); + } + return; + } + + // we should create next window if we see that + // currentUnsentWindow is terminated and nextWindowIndex is + // greater than currentWindowIndex + if (nextWindowIndex > activeWindowIndex && (InnerWindow.isTimeout(previousInnerWindowState) || InnerWindow.isTerminated(previousInnerWindowState))) { + final boolean shouldBeUnsent = n == 0; + final InnerWindow nextWindow = + new InnerWindow<>(this.maxSize, this, + nextWindowIndex, shouldBeUnsent, logger); + + this.window = nextWindow; + + if (!shouldBeUnsent) { + this.actual.onNext(nextWindow); + + if (n != Long.MAX_VALUE) { + REQUESTED.decrementAndGet(this); + } + } + + previousState = commitWork(this, expectedState, shouldBeUnsent); + expectedState = + (((previousState &~ACTIVE_WINDOW_INDEX_MASK) &~ HAS_UNSENT_WINDOW) ^ (expectedState == previousState ? HAS_WORK_IN_PROGRESS : 0)) | + incrementActiveWindowIndex(previousState) | + (shouldBeUnsent ? HAS_UNSENT_WINDOW : 0); + // we need to put unsent flag here because we check at the + // beginning of the loop the presence for unsent flag from + // the previousState + previousState = (previousState &~ HAS_UNSENT_WINDOW) | (shouldBeUnsent ? HAS_UNSENT_WINDOW : 0); + + if (isCancelled(expectedState)) { + nextWindow.sendCancel(); + if (shouldBeUnsent) { + nextWindow.cancel(); + } + return; + } + + if (isTerminated(expectedState) && !shouldBeUnsent) { + final Throwable e = this.error; + if (e != null) { + nextWindow.sendError(e); + this.actual.onError(e); + } + else { + nextWindow.sendComplete(); + this.actual.onComplete(); + } + return; + } + + try { + nextWindow.scheduleTimeout(); + } + catch (Exception e) { + if (hasWorkInProgress(expectedState)) { + this.actual.onError(Operators.onOperatorError(this.s, e, this.actual.currentContext())); + } + else { + this.onError(Operators.onOperatorError(this.s, e, this.actual.currentContext())); + } + + return; + } + + final long nextRequest = InnerWindow.received(previousInnerWindowState); + if (nextRequest > 0) { + this.s.request(nextRequest); + } + + + if (!hasWorkInProgress(expectedState)) { + return; + } + } else { + previousState = commitSent(this, expectedState); + expectedState = (previousState &~ HAS_UNSENT_WINDOW) ^ (expectedState == previousState ? HAS_WORK_IN_PROGRESS : 0); + previousState &= ~HAS_UNSENT_WINDOW; + + if (isCancelled(expectedState)) { + return; + } + + if (isTerminated(expectedState)) { + final Throwable e = this.error; + if (e != null) { + this.actual.onError(e); + } + else { + this.actual.onComplete(); + } + return; + } + + if (!hasWorkInProgress(expectedState)) { + return; + } + } + } else { + final InnerWindow nextWindow = + new InnerWindow<>(this.maxSize, this, nextWindowIndex, + false, logger); + + final InnerWindow previousWindow = this.window; + + this.window = nextWindow; + + this.actual.onNext(nextWindow); + + if (n != Long.MAX_VALUE) { + REQUESTED.decrementAndGet(this); + } + + previousState = commitWork(this, expectedState, false); + expectedState = + (((previousState &~ACTIVE_WINDOW_INDEX_MASK) &~ HAS_UNSENT_WINDOW) ^ (expectedState == previousState ? HAS_WORK_IN_PROGRESS : 0)) | + incrementActiveWindowIndex(previousState); + + if (isCancelled(expectedState)) { + previousWindow.sendCancel(); + nextWindow.sendCancel(); + return; + } + + if (isTerminated(expectedState)) { + final Throwable e = this.error; + if (e != null) { + previousWindow.sendError(e); + nextWindow.sendError(e); + this.actual.onError(e); + } + else { + previousWindow.sendComplete(); + nextWindow.sendComplete(); + this.actual.onComplete(); + } + return; + } + + try { + nextWindow.scheduleTimeout(); + } + catch (Exception e) { + if (hasWorkInProgress(expectedState)) { + this.actual.onError(Operators.onOperatorError(this.s, e, this.actual.currentContext())); + } + else { + this.onError(Operators.onOperatorError(this.s, e, this.actual.currentContext())); + } + + return; + } + + final long nextRequest; + if (previousWindow == null) { + // possible at the very beginning + nextRequest = this.maxSize; + } + else { + long previousActiveWindowState = previousWindow.sendComplete(); + nextRequest = InnerWindow.received(previousActiveWindowState); + } + + if (nextRequest > 0) { + this.s.request(nextRequest); + } + + if (!hasWorkInProgress(expectedState)) { + return; + } + } + } + else if (n == 0 && !hasUnsentWindow) { + final InnerWindow nextWindow = + new InnerWindow<>(this.maxSize, this, nextWindowIndex, true, logger); + + final InnerWindow previousWindow = this.window; + this.window = nextWindow; + + // doesn't propagate through onNext since window is unsent + + previousState = commitWork(this, expectedState, true); + expectedState = + (((previousState &~ACTIVE_WINDOW_INDEX_MASK) &~ HAS_UNSENT_WINDOW) ^ (expectedState == previousState ? HAS_WORK_IN_PROGRESS : 0)) | + incrementActiveWindowIndex(previousState) | + HAS_UNSENT_WINDOW; + previousState |= HAS_UNSENT_WINDOW; + + if (isCancelled(expectedState)) { + previousWindow.sendCancel(); + nextWindow.sendCancel(); + nextWindow.cancel(); + return; + } + + // window is deliberately unsent, we can not deliver it since no + // demand even if it is terminated + + try { + nextWindow.scheduleTimeout(); + } + catch (Exception e) { + if (hasWorkInProgress(expectedState)) { + this.actual.onError(Operators.onOperatorError(this.s, e, this.actual.currentContext())); + } + else { + this.onError(Operators.onOperatorError(this.s, e, this.actual.currentContext())); + } + + return; + } + + long previousActiveWindowState = previousWindow.sendComplete(); + final long nextRequest = InnerWindow.received(previousActiveWindowState); + + if (nextRequest > 0) { + this.s.request(nextRequest); + } + + if (!hasWorkInProgress(expectedState)) { + return; + } + } + else { + expectedState = markWorkDone(this, expectedState); + previousState = expectedState | HAS_WORK_IN_PROGRESS; + + if (isCancelled(expectedState)) { + final InnerWindow currentWindow = this.window; + final long previousWindowState = currentWindow.sendCancel(); + if (!InnerWindow.isSent(previousWindowState)) { + currentWindow.cancel(); + } + return; + } + + if (isTerminated(expectedState) && !hasUnsentWindow(expectedState)) { + final Throwable e = this.error; + if (e != null) { + this.actual.onError(e); + } + else { + this.actual.onComplete(); + } + return; + } + + if (!hasWorkInProgress(expectedState)) { + return; + } + } + } + } + + @Override + public void cancel() { + final long previousState = markCancelled(this); + if ((!hasWorkInProgress(previousState) && isTerminated(previousState) && !hasUnsentWindow(previousState)) || isCancelled(previousState)) { + return; + } + + this.s.cancel(); + + final InnerWindow currentActiveWindow = this.window; + if (currentActiveWindow != null) { + if (!InnerWindow.isSent(currentActiveWindow.sendCancel())) { + if (!hasWorkInProgress(previousState)) { + currentActiveWindow.cancel(); + } + } + } + } + + Disposable schedule(Runnable runnable, long createTime) { + final long delayedNanos = scheduler.now(TimeUnit.NANOSECONDS) - createTime; + + final long timeSpanInNanos = unit.toNanos(timespan); + final long newTimeSpanInNanos = timeSpanInNanos - delayedNanos; + if (newTimeSpanInNanos > 0) { + return worker.schedule(runnable, timespan, unit); + } else { + runnable.run(); + return InnerWindow.DISPOSED; + } + } + + long now() { + return scheduler.now(TimeUnit.NANOSECONDS); + } + + static boolean hasUnsentWindow(long state) { + return (state & HAS_UNSENT_WINDOW) == HAS_UNSENT_WINDOW; + } + + static boolean isCancelled(long state) { + return (state & CANCELLED_FLAG) == CANCELLED_FLAG; + } + + static boolean isTerminated(long state) { + return (state & TERMINATED_FLAG) == TERMINATED_FLAG; + } + + static boolean hasWorkInProgress(long state) { + return (state & HAS_WORK_IN_PROGRESS) == HAS_WORK_IN_PROGRESS; + } + + static long incrementRequestIndex(long state) { + return ((((state & REQUEST_INDEX_MASK) >> REQUEST_INDEX_SHIFT) + 1) << REQUEST_INDEX_SHIFT) & REQUEST_INDEX_MASK; + } + + static long incrementActiveWindowIndex(long state) { + return ((((state & ACTIVE_WINDOW_INDEX_MASK) >> ACTIVE_WINDOW_INDEX_SHIFT) + 1) << ACTIVE_WINDOW_INDEX_SHIFT) & ACTIVE_WINDOW_INDEX_MASK; + } + + static int activeWindowIndex(long state) { + return (int) ((state & ACTIVE_WINDOW_INDEX_MASK) >> ACTIVE_WINDOW_INDEX_SHIFT); + } + + static long incrementNextWindowIndex(long state) { + return ((state & NEXT_WINDOW_INDEX_MASK) + 1) & NEXT_WINDOW_INDEX_MASK; + } + + static int nextWindowIndex(long state) { + return (int) (state & NEXT_WINDOW_INDEX_MASK); + } + + + + /** + * Adds {@link #TERMINATED_FLAG} to indicate cancellation fact. Operation fails + * if current state is already terminated or cancelled + * + * @param instance from which to read state + * + * @return previous state + */ + static long markTerminated(WindowTimeoutWithBackpressureSubscriber instance) { + for(;;) { + final long previousState = instance.state; + + if (isTerminated(previousState) || isCancelled(previousState)) { + return previousState; + } + + final long nextState = previousState | TERMINATED_FLAG; + if (STATE.compareAndSet(instance, previousState, nextState)) { + if (instance.logger != null) { + instance.logger.log(instance.toString(), "mtd", previousState, nextState); + } + return previousState; + } + } + } + + /** + * Adds {@link #CANCELLED_FLAG} to indicate cancellation fact. Operation fails + * if current state is already cancelled, or it has no work-in-progress and + * is-terminated and has no unsent window + * + * @param instance from which to read state + * + * @return previous state + */ + static long markCancelled(WindowTimeoutWithBackpressureSubscriber instance) { + for (;;) { + final long previousState = instance.state; + if ((!hasWorkInProgress(previousState) && isTerminated(previousState) && !hasUnsentWindow(previousState)) || isCancelled(previousState)) { + return previousState; + } + + final long nextState = previousState | CANCELLED_FLAG; + if (STATE.compareAndSet(instance, previousState, nextState)) { + return previousState; + } + } + } + + /** + * Removes {@link #HAS_WORK_IN_PROGRESS} to indicate no work-in-progress. + * Operation fails if current state does not equeal to the expected one + * + * @param instance from which to read state + * + * @return current state if fail or next state if successfully applied + */ + static long markWorkDone(WindowTimeoutWithBackpressureSubscriber instance, long expectedState) { + for (;;) { + final long currentState = instance.state; + + if (expectedState != currentState) { + if (instance.logger != null) { + instance.logger.log(instance.toString(), "fwd", currentState, currentState); + } + return currentState; + } + + final long nextState = currentState ^ HAS_WORK_IN_PROGRESS; + if (STATE.compareAndSet(instance, currentState, nextState)) { + if (instance.logger != null) { + instance.logger.log(instance.toString(), "mwd", currentState, nextState); + } + return nextState; + } + } + } + + /** + * Commits fact that an unsent window is delivered. This + * operation tries to remove {@link #HAS_WORK_IN_PROGRESS} flag if current state + * is equal to the expected one. + * + * @param instance from which to read state + * + * @return previous state + */ + static long commitSent(WindowTimeoutWithBackpressureSubscriber instance, long expectedState) { + for (;;) { + final long currentState = instance.state; + + final long clearState = (currentState &~ HAS_UNSENT_WINDOW); + final long nextState = (clearState ^ (expectedState == currentState ? HAS_WORK_IN_PROGRESS : 0)); + + if (STATE.compareAndSet(instance, currentState, nextState)) { + if (instance.logger != null) { + instance.logger.log(instance.toString(), "cts", currentState, nextState); + } + return currentState; + } + } + } + + /** + * Commits new active window index and removes {@link #HAS_UNSENT_WINDOW} if + * specified. This operation tries to remove {@link #HAS_WORK_IN_PROGRESS} flag + * if current state is equal to the expected one. + * + * @param instance from which to read state + * + * @return previous state + */ + static long commitWork(WindowTimeoutWithBackpressureSubscriber instance, long expectedState, boolean setUnsentFlag) { + for (;;) { + final long currentState = instance.state; + + final long clearState = ((currentState &~ACTIVE_WINDOW_INDEX_MASK) &~ HAS_UNSENT_WINDOW); + final long nextState = (clearState ^ (expectedState == currentState ? HAS_WORK_IN_PROGRESS : 0)) | + incrementActiveWindowIndex(currentState) | + (setUnsentFlag ? HAS_UNSENT_WINDOW : 0); + + if (STATE.compareAndSet(instance, currentState, nextState)) { + if (instance.logger != null) { + instance.logger.log(instance.toString(), "ctw", currentState, nextState); + } + return currentState; + } + } + } + } + + static final class InnerWindow extends Flux + implements InnerProducer, Runnable { + + static final Disposable DISPOSED = Disposables.disposed(); + + + @Nullable + final StateLogger logger; + + final WindowTimeoutWithBackpressureSubscriber parent; + final int max; + final Queue queue; + final long createTime; + final int index; + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(InnerWindow.class, "requested"); + + volatile long state; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater STATE = + AtomicLongFieldUpdater.newUpdater(InnerWindow.class, "state"); + + volatile Disposable timer; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater TIMER = + AtomicReferenceFieldUpdater.newUpdater(InnerWindow.class, Disposable.class, "timer"); + + + CoreSubscriber actual; + + Throwable error; + + int received = 0; + int produced = 0; + + InnerWindow( + int max, + WindowTimeoutWithBackpressureSubscriber parent, + int index, + boolean markUnsent, + @Nullable StateLogger logger) { + this.max = max; + this.parent = parent; + this.queue = Queues.get(max).get(); + this.index = index; + this.logger = logger; + + if (markUnsent) { + STATE.lazySet(this, UNSENT_STATE); + if (this.logger != null) { + this.logger.log(this.toString(), "mct", 0, UNSENT_STATE); + } + } else { + if (this.logger != null) { + this.logger.log(this.toString(), "mct", 0, 0); + } + } + + this.createTime = parent.now(); + } + + @Override + public void subscribe(CoreSubscriber actual) { + long previousState = markSubscribedOnce(this); + if (hasSubscribedOnce(previousState)) { + Operators.error(actual, new IllegalStateException("Only one subscriber allowed")); + return; + } + + this.actual = actual; + + actual.onSubscribe(this); + + previousState = markSubscriberSet(this); + if (isFinalized(previousState) || hasWorkInProgress(previousState)) { + return; + } + + if (!hasValues(previousState) && isTerminated(previousState)) { + final Throwable t = this.error; + if (t != null) { + actual.onError(t); + } else { + actual.onComplete(); + } + } + } + + @Override + public CoreSubscriber actual() { + return this.actual; + } + + @Override + public void request(long n) { + Operators.addCap(REQUESTED, this, n); + final long previousState = markHasRequest(this); + + if (hasWorkInProgress(previousState) || isCancelled(previousState) || isFinalized(previousState)) { + return; + } + + if (hasValues(previousState)) { + drain((previousState | HAS_SUBSCRIBER_SET_STATE) + 1); + } + } + + @Override + public void cancel() { + final long previousState = markCancelled(this); + if (isCancelled(previousState) || isFinalized(previousState) || hasWorkInProgress(previousState)) { + return; + } + + clearAndFinalize(); + } + + /** + * This method is called from the parent when the parent is cancelled. We don't + * send any exceptions to the subscriber here but we deliver complete window + * and discard all further values delivered through the {@link #sendNext} method + * + * @return previous state + */ + long sendCancel() { + for (;;) { + final long state = this.state; + + if (isCancelledByParent(state)) { + return state; + } + + final long cleanState = state & ~WORK_IN_PROGRESS_MAX; + final long nextState = cleanState | + TERMINATED_STATE | + PARENT_CANCELLED_STATE | + ( + hasSubscriberSet(state) + ? hasValues(state) + ? incrementWork(state & WORK_IN_PROGRESS_MAX) + : FINALIZED_STATE + : 0 + ); + + if (STATE.compareAndSet(this, state, nextState)) { + + if (isFinalized(state)) { + return state; + } + + if (!isTimeout(state)) { + final Disposable timer = TIMER.getAndSet(this, DISPOSED); + if (timer != null) { + timer.dispose(); + } + } + + if (hasSubscriberSet(state)) { + if (hasWorkInProgress(state)) { + return state; + } + + if (isCancelled(state)) { + clearAndFinalize(); + return state; + } + + if (hasValues(state)) { + drain(nextState); + } else { + this.actual.onComplete(); + } + } + + return state; + } + } + } + + boolean sendNext(T t) { + final int received = this.received + 1 ; + if (received > this.max) { + return false; + } + this.received = received; + + this.queue.offer(t); + + final long previousState; + final long nextState; + if (received == this.max) { + previousState = markHasValuesAndTerminated(this); + nextState = (previousState | TERMINATED_STATE | HAS_VALUES_STATE) + 1; + + if (!isTimeout(previousState)) { + final Disposable timer = TIMER.getAndSet(this, DISPOSED); + if (timer != null) { + timer.dispose(); + } + + if (!isCancelledByParent(previousState)) { + this.parent.tryCreateNextWindow(this.index); + } + } + } else { + previousState = markHasValues(this); + nextState = (previousState | HAS_VALUES_STATE) + 1; + } + + if (isFinalized(previousState)) { + if (isCancelledByParent(previousState)) { + clearQueue(); + return true; + } + else if (isCancelled(previousState)) { + clearQueue(); + // doing extra request since index progress was not committed but + // value is discarded + this.parent.s.request(1); + return true; + } + else { + if (this.queue.poll() != t) { + // doing extra request since the value is sent event though the + // index progress was not committed + this.parent.s.request(1); + return true; + } + else { + return false; + } + } + } + + if (isTimeout(previousState) && isTerminated(previousState)) { + // doing extra request since the value being sent is not replenished + this.parent.s.request(1); + } + + if (hasSubscriberSet(previousState)) { + if (hasWorkInProgress(previousState)) { + return true; + } + + if (isCancelled(previousState)) { + clearAndFinalize(); + return true; + } + + drain(nextState); + } + + return true; + } + + long sendComplete() { + final long previousState = markTerminated(this); + if (isFinalized(previousState) || isTerminated(previousState)) { + return previousState; + } + + if (!isTimeout(previousState)) { + final Disposable timer = TIMER.getAndSet(this, DISPOSED); + + if (timer != null) { + timer.dispose(); + } + } + + if (hasSubscriberSet(previousState)) { + if (hasWorkInProgress(previousState)) { + return previousState; + } + + if (isCancelled(previousState)) { + clearAndFinalize(); + return previousState; + } + + if (hasValues(previousState)) { + drain((previousState | TERMINATED_STATE) + 1); + } else { + this.actual.onComplete(); + } + } + + return previousState; + } + + long sendError(Throwable error) { + this.error = error; + + final long previousState = markTerminated(this); + if (isFinalized(previousState) || isTerminated(previousState)) { + return previousState; + } + + if (!isTimeout(previousState)) { + final Disposable timer = TIMER.getAndSet(this, DISPOSED); + + if (timer != null) { + timer.dispose(); + } + } + + if (hasSubscriberSet(previousState)) { + if (hasWorkInProgress(previousState)) { + return previousState; + } + + if (isCancelled(previousState)) { + clearAndFinalize(); + return previousState; + } + + if (hasValues(previousState)) { + drain((previousState | TERMINATED_STATE) + 1); + } else { + this.actual.onError(error); + } + } + + return previousState; + } + + long sendSent() { + final long previousState = markSent(this); + if (isFinalized(previousState) || !isTerminated(previousState) && !isTimeout(previousState)) { + return previousState; + } + + if (hasSubscriberSet(previousState)) { + if (hasWorkInProgress(previousState)) { + return previousState; + } + + if (isCancelled(previousState)) { + clearAndFinalize(); + return previousState; + } + + if (hasValues(previousState)) { + drain(((previousState ^ UNSENT_STATE) | TERMINATED_STATE) + 1); + } else { + this.actual.onComplete(); + } + } + + return previousState; + } + + void drain(long expectedState) { + final Queue q = this.queue; + final CoreSubscriber a = this.actual; + + for (; ; ) { + long r = this.requested; + + int e = 0; + boolean empty = false; + while (e < r) { + final T v = q.poll(); + + empty = v == null; + if (checkTerminated((this.produced + e), a, v)) { + return; + } + + if (empty) { + break; + } + + a.onNext(v); + + e++; + } + + this.produced += e; + + if (checkTerminated(this.produced, a, null)) { + return; + } + + if (e != 0 && r != Long.MAX_VALUE) { + REQUESTED.addAndGet(this, -e); + } + + expectedState = markWorkDone(this, expectedState, !empty); + if (isCancelled(expectedState)) { + clearAndFinalize(); + return; + } + + if (!hasWorkInProgress(expectedState)) { + return; + } + } + } + + boolean checkTerminated( + int totalProduced, + CoreSubscriber actual, + @Nullable T value) { + final long state = this.state; + if (isCancelled(state)) { + if (value != null) { + Operators.onDiscard(value, actual.currentContext()); + } + clearAndFinalize(); + return true; + } + + if (value == null && received(state) <= totalProduced && isTerminated(state)) { + if (!markFinalized(state)) { + return false; + } + + final Throwable e = this.error; + if (e != null) { + actual.onError(e); + } + else { + actual.onComplete(); + } + + return true; + } + + return false; + } + + void scheduleTimeout() { + final Disposable nextTimer = parent.schedule(this, this.createTime); + if (!TIMER.compareAndSet(this, null, nextTimer)) { + nextTimer.dispose(); + } + } + + @Override + public void run() { + long previousState = markTimeout(this); + if (isTerminated(previousState) || isCancelledByParent(previousState)) { + return; + } + + this.parent.tryCreateNextWindow(this.index); + } + + void clearAndFinalize() { + for (; ; ) { + long state = this.state; + + clearQueue(); + + if (isFinalized(state)) { + return; + } + + final long nextState = ((state | FINALIZED_STATE) & ~WORK_IN_PROGRESS_MAX) ^ (hasValues(state) ? HAS_VALUES_STATE : 0); + if (STATE.compareAndSet(this, state, nextState)) { + return; + } + } + } + + boolean markFinalized(long state) { + final long nextState = ((state | FINALIZED_STATE) & ~WORK_IN_PROGRESS_MAX) ^ ( + hasValues(state) ? HAS_VALUES_STATE : 0); + if (STATE.compareAndSet(this, state, nextState)) { + return true; + } + + return false; + } + + void clearQueue() { + final Queue q = this.queue; + final Context context = this.actual != null ? this.actual.currentContext() : this.parent.currentContext(); + + T v; + while ((v = q.poll()) != null) { + Operators.onDiscard(v, context); + } + } + + static final long FINALIZED_STATE = + 0b1000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000L; + static final long TERMINATED_STATE = + 0b0100_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000L; + static final long PARENT_CANCELLED_STATE = + 0b0010_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000L; + static final long CANCELLED_STATE = + 0b0001_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000L; + static final long TIMEOUT_STATE = + 0b0000_1000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000L; + static final long HAS_VALUES_STATE = + 0b0000_0100_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000L; + static final long HAS_SUBSCRIBER_STATE = + 0b0000_0010_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000L; + static final long HAS_SUBSCRIBER_SET_STATE = + 0b0000_0001_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000L; + static final long UNSENT_STATE = + 0b0000_0000_1000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000L; + static final long RECEIVED_MASK = + 0b0000_0000_0111_1111_1111_1111_1111_1111_1111_1111_0000_0000_0000_0000_0000_0000L; + static final long WORK_IN_PROGRESS_MAX = + 0b0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_1111_1111_1111_1111_1111_1111L; + static final long RECEIVED_SHIFT_BITS = 24; + + static long markSent(InnerWindow instance) { + for (; ; ) { + final long state = instance.state; + + if (isCancelled(state)) { + return state; + } + + final long nextState = + (state ^ UNSENT_STATE) | + ((isTimeout(state) || isTerminated(state)) + ? TERMINATED_STATE | ( + hasSubscriberSet(state) + ? hasValues(state) + ? incrementWork(state & WORK_IN_PROGRESS_MAX) + : FINALIZED_STATE + : 0 + ) + : 0); + if (STATE.compareAndSet(instance, state, nextState)) { + if (instance.logger != null) { + instance.logger.log(instance.toString(), "mst", state, + nextState); + } + return state; + } + } + } + + static boolean isSent(long state) { + return (state & UNSENT_STATE) == 0; + } + + static long markTimeout(InnerWindow instance) { + for (; ; ) { + final long state = instance.state; + + if (isTerminated(state)) { + return state; + } + + final long nextState = state | TIMEOUT_STATE; + if (STATE.compareAndSet(instance, state, nextState)) { + if (instance.logger != null) { + instance.logger.log(instance.toString(), "mtt", state, nextState); + } + return state; + } + } + } + + static boolean isTimeout(long state) { + return (state & TIMEOUT_STATE) == TIMEOUT_STATE; + } + + static long markCancelled(InnerWindow instance) { + for (; ; ) { + final long state = instance.state; + + if (isCancelled(state) || isFinalized(state)) { + return state; + } + + final long cleanState = state & ~WORK_IN_PROGRESS_MAX; + final long nextState = + cleanState | CANCELLED_STATE | HAS_SUBSCRIBER_SET_STATE | incrementWork(state & WORK_IN_PROGRESS_MAX); + if (STATE.compareAndSet(instance, state, nextState)) { + if (instance.logger != null) { + instance.logger.log(instance.toString(), "mcd", state, + nextState); + } + return state; + } + } + } + + static boolean isCancelled(long state) { + return (state & CANCELLED_STATE) == CANCELLED_STATE; + } + + static boolean isCancelledByParent(long state) { + return (state & PARENT_CANCELLED_STATE) == PARENT_CANCELLED_STATE; + } + + static long markHasValues(InnerWindow instance) { + for (; ; ) { + final long state = instance.state; + + if (isFinalized(state)) { + if (instance.logger != null) { + instance.logger.log(instance.toString(), "fhv", state, state); + } + return state; + } + + final long nextState; + if (hasSubscriberSet(state)) { + final long cleanState = + (state & ~WORK_IN_PROGRESS_MAX) & ~RECEIVED_MASK; + nextState = cleanState | + HAS_VALUES_STATE | + incrementReceived(state & RECEIVED_MASK) | + incrementWork(state & WORK_IN_PROGRESS_MAX); + } + else { + final long cleanState = + (state & ~WORK_IN_PROGRESS_MAX) & ~RECEIVED_MASK; + nextState = cleanState | + HAS_VALUES_STATE | + incrementReceived(state & RECEIVED_MASK) | + (hasWorkInProgress(state) ? incrementWork(state & WORK_IN_PROGRESS_MAX) : 0); + } + + if (STATE.compareAndSet(instance, state, nextState)) { + if (instance.logger != null) { + instance.logger.log(instance.toString(), "mhv", state, + nextState); + } + return state; + } + } + } + + static long markHasValuesAndTerminated(InnerWindow instance) { + for (;;) { + final long state = instance.state; + + if (isFinalized(state)) { + if (instance.logger != null) { + instance.logger.log(instance.toString(), "fht", state, state); + } + return state; + } + + final long cleanState = + (state & ~WORK_IN_PROGRESS_MAX) & ~RECEIVED_MASK; + final long nextState; + if (hasSubscriberSet(state)) { + nextState = cleanState | + HAS_VALUES_STATE | + TERMINATED_STATE | + incrementReceived(state & RECEIVED_MASK) | + incrementWork(state & WORK_IN_PROGRESS_MAX); + } + else { + nextState = cleanState | + HAS_VALUES_STATE | + TERMINATED_STATE | + incrementReceived(state & RECEIVED_MASK) | + (hasWorkInProgress(state) ? incrementWork(state & WORK_IN_PROGRESS_MAX) : 0); + } + + if (STATE.compareAndSet(instance, state, nextState)) { + if (instance.logger != null) { + instance.logger.log(instance.toString(), "hvt", state, + nextState); + } + return state; + } + } + } + + static boolean hasValues(long state) { + return (state & HAS_VALUES_STATE) == HAS_VALUES_STATE; + } + + static long received(long state) { + return ((state & RECEIVED_MASK) >> RECEIVED_SHIFT_BITS); + } + + static long markHasRequest(InnerWindow instance) { + for (; ; ) { + final long state = instance.state; + + if (isCancelled(state) || isFinalized(state)) { + return state; + } + + final long cleanState = state & ~WORK_IN_PROGRESS_MAX; + final long nextState = cleanState | + HAS_SUBSCRIBER_SET_STATE | + ( + hasValues(state) + ? incrementWork(state & WORK_IN_PROGRESS_MAX) + : 0 + ); + + if (STATE.compareAndSet(instance, state, nextState)) { + if (instance.logger != null) { + instance.logger.log(instance.toString(), "mhr", state, nextState); + } + return state; + } + } + } + + static long markTerminated(InnerWindow instance) { + for (; ; ) { + final long state = instance.state; + + if (isFinalized(state) || isTerminated(state)) { + return state; + } + + final long cleanState = state & ~WORK_IN_PROGRESS_MAX; + final long nextState = + cleanState | + TERMINATED_STATE | + ( + hasSubscriberSet(state) + ? hasValues(state) + ? incrementWork(state & WORK_IN_PROGRESS_MAX) + : FINALIZED_STATE + : 0 + ); + if (STATE.compareAndSet(instance, state, nextState)) { + if (instance.logger != null) { + instance.logger.log(instance.toString(), "mtd", state, nextState); + } + return state; + } + } + } + + static boolean isTerminated(long state) { + return (state & TERMINATED_STATE) == TERMINATED_STATE; + } + + static long incrementWork(long currentWork) { + return currentWork == WORK_IN_PROGRESS_MAX + ? 1 + : (currentWork + 1); + } + + static long incrementReceived(long currentWork) { + return ((currentWork >> RECEIVED_SHIFT_BITS) + 1) << RECEIVED_SHIFT_BITS; + } + + static long markSubscribedOnce(InnerWindow instance) { + for (; ; ) { + final long state = instance.state; + + if (hasSubscribedOnce(state)) { + return state; + } + + final long nextState = state | HAS_SUBSCRIBER_STATE; + if (STATE.compareAndSet(instance, state, nextState)) { + if (instance.logger != null) { + instance.logger.log(instance.toString(), "mso", state, nextState); + } + return state; + } + } + } + + static boolean hasSubscribedOnce(long state) { + return (state & HAS_SUBSCRIBER_STATE) == HAS_SUBSCRIBER_STATE; + } + + static long markSubscriberSet(InnerWindow instance) { + for (; ; ) { + final long state = instance.state; + + if (isFinalized(state) || hasWorkInProgress(state)) { + return state; + } + + final long nextState = + (state | HAS_SUBSCRIBER_SET_STATE) | (isTerminated(state) && !hasValues(state) ? FINALIZED_STATE : 0); + if (STATE.compareAndSet(instance, state, nextState)) { + if (instance.logger != null) { + instance.logger.log(instance.toString(), "mss", state, nextState); + } + return state; + } + } + } + + static boolean hasSubscriberSet(long state) { + return (state & HAS_SUBSCRIBER_SET_STATE) == HAS_SUBSCRIBER_SET_STATE; + } + + static long markWorkDone(InnerWindow instance, + long expectedState, + boolean hasValues) { + final long state = instance.state; + + if (expectedState != state) { + return state; + } + + final long nextState = + (state ^ (hasValues ? 0 : HAS_VALUES_STATE)) &~ WORK_IN_PROGRESS_MAX; + if (STATE.compareAndSet(instance, state, nextState)) { + if (instance.logger != null) { + instance.logger.log(instance.toString(), "mwd", state, nextState); + } + return nextState; + } + + return instance.state; + } + + static boolean hasWorkInProgress(long state) { + return (state & WORK_IN_PROGRESS_MAX) > 0; + } + + static boolean isFinalized(long state) { + return (state & FINALIZED_STATE) == FINALIZED_STATE; + } + + @Override + public String toString() { + return super.toString() + " " + index; + } + } + static final class WindowTimeoutSubscriber implements InnerOperator> { final CoreSubscriber> actual; @@ -88,13 +1717,13 @@ volatile boolean done; volatile boolean cancelled; - volatile long requested; + volatile long requested; @SuppressWarnings("rawtypes") static final AtomicLongFieldUpdater REQUESTED = AtomicLongFieldUpdater.newUpdater(WindowTimeoutSubscriber.class, "requested"); - volatile int wip; + volatile int wip; @SuppressWarnings("rawtypes") static final AtomicIntegerFieldUpdater WIP = AtomicIntegerFieldUpdater.newUpdater(WindowTimeoutSubscriber.class, @@ -109,18 +1738,22 @@ volatile boolean terminated; - volatile Disposable timer; + volatile Disposable timer; @SuppressWarnings("rawtypes") - static final AtomicReferenceFieldUpdater TIMER = - AtomicReferenceFieldUpdater.newUpdater(WindowTimeoutSubscriber.class, Disposable.class, "timer"); + static final AtomicReferenceFieldUpdater + TIMER = AtomicReferenceFieldUpdater.newUpdater( + WindowTimeoutSubscriber.class, + Disposable.class, + "timer"); WindowTimeoutSubscriber(CoreSubscriber> actual, int maxSize, long timespan, TimeUnit unit, Scheduler scheduler) { this.actual = actual; - this.queue = Queues.unboundedMultiproducer().get(); + this.queue = Queues.unboundedMultiproducer() + .get(); this.timespan = timespan; this.unit = unit; this.scheduler = scheduler; @@ -141,14 +1774,30 @@ @Override public Object scanUnsafe(Attr key) { - if (key == Attr.PARENT) return s; - if (key == Attr.CANCELLED) return cancelled; - if (key == Attr.TERMINATED) return done; - if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; - if (key == Attr.CAPACITY) return maxSize; - if (key == Attr.BUFFERED) return queue.size(); - if (key == Attr.RUN_ON) return worker; - if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; + if (key == Attr.PARENT) { + return s; + } + if (key == Attr.CANCELLED) { + return cancelled; + } + if (key == Attr.TERMINATED) { + return done; + } + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) { + return requested; + } + if (key == Attr.CAPACITY) { + return maxSize; + } + if (key == Attr.BUFFERED) { + return queue.size(); + } + if (key == Attr.RUN_ON) { + return worker; + } + if (key == Attr.RUN_STYLE) { + return Attr.RunStyle.ASYNC; + } return InnerOperator.super.scanUnsafe(key); } @@ -165,7 +1814,10 @@ return; } - Sinks.Many w = Sinks.unsafe().many().unicast().onBackpressureBuffer(); + Sinks.Many w = Sinks.unsafe() + .many() + .unicast() + .onBackpressureBuffer(); window = w; long r = requested; @@ -177,7 +1829,8 @@ } else { a.onError(Operators.onOperatorError(s, - Exceptions.failWithOverflow(), actual.currentContext())); + Exceptions.failWithOverflow(), + actual.currentContext())); return; } @@ -193,7 +1846,11 @@ this), timespan, timespan, unit); } catch (Exception e) { - actual.onError(Operators.onRejectedExecution(e, s, null, null, actual.currentContext())); + actual.onError(Operators.onRejectedExecution(e, + s, + null, + null, + actual.currentContext())); return Disposables.disposed(); } } @@ -219,7 +1876,10 @@ long r = requested; if (r != 0L) { - w = Sinks.unsafe().many().unicast().onBackpressureBuffer(); + w = Sinks.unsafe() + .many() + .unicast() + .onBackpressureBuffer(); window = w; actual.onNext(w.asFlux()); if (r != Long.MAX_VALUE) { @@ -238,8 +1898,9 @@ else { window = null; actual.onError(Operators.onOperatorError(s, - Exceptions.failWithOverflow(), t, actual - .currentContext())); + Exceptions.failWithOverflow(), + t, + actual.currentContext())); timer.dispose(); worker.dispose(); return; @@ -289,7 +1950,7 @@ @Override public void request(long n) { - if(Operators.validate(n)) { + if (Operators.validate(n)) { Operators.addCap(REQUESTED, this, n); } } @@ -346,7 +2007,10 @@ if (isHolder) { w.emitComplete(Sinks.EmitFailureHandler.FAIL_FAST); count = 0; - w = Sinks.unsafe().many().unicast().onBackpressureBuffer(); + w = Sinks.unsafe() + .many() + .unicast() + .onBackpressureBuffer(); window = w; long r = requested; @@ -360,7 +2024,8 @@ window = null; queue.clear(); a.onError(Operators.onOperatorError(s, - Exceptions.failWithOverflow(), actual.currentContext())); + Exceptions.failWithOverflow(), + actual.currentContext())); timer.dispose(); worker.dispose(); return; @@ -380,7 +2045,10 @@ long r = requested; if (r != 0L) { - w = Sinks.unsafe().many().unicast().onBackpressureBuffer(); + w = Sinks.unsafe() + .many() + .unicast() + .onBackpressureBuffer(); window = w; actual.onNext(w.asFlux()); if (r != Long.MAX_VALUE) { @@ -399,8 +2067,9 @@ else { window = null; a.onError(Operators.onOperatorError(s, - Exceptions.failWithOverflow(), o, actual - .currentContext())); + Exceptions.failWithOverflow(), + o, + actual.currentContext())); timer.dispose(); worker.dispose(); return; Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxWindowWhen.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxWindowWhen.java (.../FluxWindowWhen.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxWindowWhen.java (.../FluxWindowWhen.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,6 +36,8 @@ import reactor.util.annotation.Nullable; import reactor.util.concurrent.Queues; +import static reactor.core.Exceptions.wrapSource; + /** * Splits the source sequence into potentially overlapping windowEnds controlled by items * of a start Publisher and end Publishers derived from the start values. @@ -235,15 +237,16 @@ dispose(); Throwable e = error; if (e != null) { - actual.onError(e); for (Sinks.Many w : ws) { - w.emitError(e, Sinks.EmitFailureHandler.FAIL_FAST); + w.emitError(wrapSource(e), + Sinks.EmitFailureHandler.FAIL_FAST); } + actual.onError(e); } else { - actual.onComplete(); for (Sinks.Many w : ws) { w.emitComplete(Sinks.EmitFailureHandler.FAIL_FAST); } + actual.onComplete(); } ws.clear(); return; Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxZip.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/FluxZip.java (.../FluxZip.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxZip.java (.../FluxZip.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -297,47 +297,149 @@ if (sc != 0 && scalars != null) { if (n != sc) { ZipSingleCoordinator coordinator = - new ZipSingleCoordinator<>(s, scalars, n, zipper); + new ZipSingleCoordinator<>(s, scalars, n, sc, zipper); s.onSubscribe(coordinator); - coordinator.subscribe(n, sc, srcs); + coordinator.subscribe(n, srcs); } else { - Operators.MonoSubscriber sds = new Operators.MonoSubscriber<>(s); + s.onSubscribe(new ZipScalarCoordinator<>(s, zipper, scalars)); + } + } + else { + ZipCoordinator coordinator = + new ZipCoordinator<>(s, zipper, n, queueSupplier, prefetch); - s.onSubscribe(sds); + s.onSubscribe(coordinator); - R r; + coordinator.subscribe(srcs, n); + } + } + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.PREFETCH) return prefetch; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return null; + } + + static final class ZipScalarCoordinator implements InnerProducer, + Fuseable, + Fuseable.QueueSubscription { + + final CoreSubscriber actual; + final Function zipper; + final Object[] scalars; + + volatile int state; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater STATE = + AtomicIntegerFieldUpdater.newUpdater(ZipScalarCoordinator.class, "state"); + + boolean done; + boolean cancelled; + + ZipScalarCoordinator(CoreSubscriber actual, Function zipper, Object[] scalars) { + this.actual = actual; + this.zipper = zipper; + this.scalars = scalars; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.TERMINATED) return done; + if (key == Attr.CANCELLED) return cancelled; + if (key == Attr.BUFFERED) return scalars.length; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerProducer.super.scanUnsafe(key); + } + + @Override + public CoreSubscriber actual() { + return this.actual; + } + + @Override + public void request(long n) { + if (done) { + return; + } + done = true; + + final int state = this.state; + if (state == 0 && STATE.compareAndSet(this, 0, 1)) { + final R r; try { r = Objects.requireNonNull(zipper.apply(scalars), "The zipper returned a null value"); } catch (Throwable e) { - s.onError(Operators.onOperatorError(e, s.currentContext())); + actual.onError(Operators.onOperatorError(e, actual.currentContext())); return; } - sds.complete(r); + this.actual.onNext(r); + this.actual.onComplete(); } + } + @Override + public void cancel() { + if (cancelled) { + return; + } + cancelled = true; + + final int state = this.state; + if (state == 0 && STATE.compareAndSet(this, 0, 2)) { + final Context context = actual.currentContext(); + for (Object scalar : scalars) { + Operators.onDiscard(scalar, context); + } + } } - else { - ZipCoordinator coordinator = - new ZipCoordinator<>(s, zipper, n, queueSupplier, prefetch); - s.onSubscribe(coordinator); + @Override + public R poll() { + if (done) { + return null; + } + done = true; - coordinator.subscribe(srcs, n); + return Objects.requireNonNull(zipper.apply(scalars), "The zipper returned a null value"); } - } - @Override - public Object scanUnsafe(Attr key) { - if (key == Attr.PREFETCH) return prefetch; - if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return null; + @Override + public int requestFusion(int requestedMode) { + return requestedMode & SYNC; + } + + @Override + public int size() { + return done ? 0 : 1; + } + + @Override + public boolean isEmpty() { + return done; + } + + @Override + public void clear() { + if (done || cancelled) { + return; + } + + cancelled = true; + + final Context context = actual.currentContext(); + for (Object scalar : scalars) { + Operators.onDiscard(scalar, context); + } + } } static final class ZipSingleCoordinator extends Operators.MonoSubscriber { @@ -357,6 +459,7 @@ ZipSingleCoordinator(CoreSubscriber subscriber, Object[] scalars, int n, + int sc, Function zipper) { super(subscriber); this.zipper = zipper; @@ -368,10 +471,10 @@ } } this.subscribers = a; + WIP.lazySet(this, n - sc); } - void subscribe(int n, int sc, Publisher[] sources) { - WIP.lazySet(this, n - sc); + void subscribe(int n, Publisher[] sources) { ZipSingleSubscriber[] a = subscribers; for (int i = 0; i < n; i++) { if (wip <= 0 || isCancelled()) { @@ -392,7 +495,8 @@ void next(T value, int index) { Object[] a = scalars; a[index] = value; - if (WIP.decrementAndGet(this) == 0) { + int wip = WIP.decrementAndGet(this); + if (wip == 0) { R r; try { @@ -406,9 +510,13 @@ } complete(r); + } else if (wip < 0) { + Operators.onDiscard(value, actual.currentContext()); } } + + void error(Throwable e, int index) { if (WIP.getAndSet(this, 0) > 0) { cancelAll(); @@ -429,13 +537,31 @@ @Override public void cancel() { super.cancel(); - cancelAll(); + if (WIP.getAndSet(this, 0) > 0) { + cancelAll(); + } } @Override + protected void discard(R v) { + if (v != null) { + if (v instanceof Iterable) { + Operators.onDiscardMultiple(((Iterable) v).iterator(), true, actual.currentContext()); + } + else if (v.getClass() + .isArray()) { + Operators.onDiscardMultiple(Arrays.asList((Object[]) v), actual.currentContext()); + } + else { + Operators.onDiscard(v, actual.currentContext()); + } + } + } + + @Override @Nullable public Object scanUnsafe(Attr key) { - if (key == Attr.TERMINATED) return wip == 0; + if (key == Attr.TERMINATED) return wip == 0 && !isCancelled(); if (key == Attr.BUFFERED) return wip > 0 ? scalars.length : 0; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; @@ -453,6 +579,8 @@ s.dispose(); } } + Object[] scalars = this.scalars; + Operators.onDiscardMultiple(Arrays.asList(scalars), actual.currentContext()); } } @@ -471,6 +599,7 @@ "s"); boolean done; + boolean hasFirstValue; ZipSingleSubscriber(ZipSingleCoordinator parent, int index) { this.parent = parent; @@ -486,7 +615,7 @@ @Nullable public Object scanUnsafe(Attr key) { if (key == Attr.PARENT) return s; - if (key == Attr.TERMINATED) return done; + if (key == Attr.TERMINATED) return done || hasFirstValue; if (key == Attr.ACTUAL) return parent; if (key == Attr.CANCELLED) return s == Operators.cancelledSubscription(); if (key == Attr.BUFFERED) return parent.scalars[index] == null ? 0 : 1; @@ -507,16 +636,21 @@ public void onNext(T t) { if (done) { Operators.onNextDropped(t, parent.currentContext()); + } + + if (hasFirstValue) { + Operators.onDiscard(t, parent.currentContext()); return; } - done = true; + + hasFirstValue = true; Operators.terminate(S, this); parent.next(t, index); } @Override public void onError(Throwable t) { - if (done) { + if (hasFirstValue || done) { Operators.onErrorDropped(t, parent.currentContext()); return; } @@ -526,7 +660,7 @@ @Override public void onComplete() { - if (done) { + if (hasFirstValue || done) { return; } done = true; @@ -604,7 +738,7 @@ public void request(long n) { if (Operators.validate(n)) { Operators.addCap(REQUESTED, this, n); - drain(); + drain(null, null); } } @@ -614,6 +748,10 @@ cancelled = true; cancelAll(); + + if (WIP.getAndIncrement(this) == 0) { + discardAll(1); + } } } @@ -640,7 +778,7 @@ void error(Throwable e, int index) { if (Exceptions.addThrowable(ERROR, this, e)) { - drain(); + drain(null, null); } else { Operators.onErrorDropped(e, actual.currentContext()); @@ -653,9 +791,53 @@ } } - void drain() { + void discardAll(int m) { + final Context context = actual.currentContext(); + final Object[] values = current; + Operators.onDiscardMultiple(Arrays.asList(values), context); + Arrays.fill(values, null); - if (WIP.getAndIncrement(this) != 0) { + for (;;) { + for (ZipInner s : subscribers) { + final Queue queue = s.queue; + final int sourceMode = s.sourceMode; + + if (queue != null) { + if (sourceMode == ASYNC) { + // delegates discarding to the queue holder to ensure there is no racing on draining from the SpScQueue + queue.clear(); + } + else { + Operators.onDiscardQueueWithClear(queue, context, null); + } + } + } + + int missed = wip; + if (m == missed) { + if (WIP.compareAndSet(this, m, Integer.MIN_VALUE)) { + return; + } else { + m = wip; + } + } else { + m = missed; + } + } + } + + void drain(@Nullable ZipInner callerInner, @Nullable Object dataSignal) { + int previousWork = addWork(this); + if (previousWork != 0) { + if (callerInner != null) { + if (callerInner.sourceMode == ASYNC && previousWork == Integer.MIN_VALUE) { + callerInner.queue.clear(); + } + else if (dataSignal != null && cancelled) { + // discard given dataSignal since no more is enqueued (spec guarantees serialised onXXX calls) + Operators.onDiscard(dataSignal, actual.currentContext()); + } + } return; } @@ -674,11 +856,13 @@ while (r != e) { if (cancelled) { + discardAll(missed); return; } if (error != null) { cancelAll(); + discardAll(missed); Throwable ex = Exceptions.terminate(ERROR, this); @@ -701,6 +885,7 @@ boolean sourceEmpty = v == null; if (d && sourceEmpty) { cancelAll(); + discardAll(missed); a.onComplete(); return; @@ -717,6 +902,7 @@ actual.currentContext()); cancelAll(); + discardAll(missed); Exceptions.addThrowable(ERROR, this, ex); //noinspection ConstantConditions @@ -743,6 +929,7 @@ ex = Operators.onOperatorError(null, ex, values.clone(), actual.currentContext()); cancelAll(); + discardAll(missed); Exceptions.addThrowable(ERROR, this, ex); //noinspection ConstantConditions @@ -767,6 +954,7 @@ if (error != null) { cancelAll(); + discardAll(missed); Throwable ex = Exceptions.terminate(ERROR, this); @@ -786,6 +974,7 @@ boolean empty = v == null; if (d && empty) { cancelAll(); + discardAll(missed); a.onComplete(); return; @@ -799,6 +988,7 @@ actual.currentContext()); cancelAll(); + discardAll(missed); Exceptions.addThrowable(ERROR, this, ex); //noinspection ConstantConditions @@ -831,6 +1021,20 @@ } } } + + static int addWork(ZipCoordinator instance) { + for (;;) { + int state = instance.wip; + + if (state == Integer.MIN_VALUE) { + return Integer.MIN_VALUE; + } + + if (WIP.compareAndSet(instance, state, state + 1)) { + return state; + } + } + } } static final class ZipInner @@ -885,7 +1089,7 @@ sourceMode = SYNC; queue = f; done = true; - parent.drain(); + parent.drain(this, null); return; } else if (m == ASYNC) { @@ -900,19 +1104,21 @@ queue = queueSupplier.get(); } s.request(Operators.unboundedOrPrefetch(prefetch)); + parent.drain(this, null); } } @Override public void onNext(T t) { if (sourceMode != ASYNC) { if (!queue.offer(t)) { + Operators.onDiscard(t, currentContext()); onError(Operators.onOperatorError(s, Exceptions.failWithOverflow (Exceptions.BACKPRESSURE_ERROR_QUEUE_FULL), currentContext())); return; } } - parent.drain(); + parent.drain(this, t); } @Override @@ -933,7 +1139,7 @@ @Override public void onComplete() { done = true; - parent.drain(); + parent.drain(this, null); } @Override @@ -943,7 +1149,7 @@ if (key == Attr.ACTUAL) return parent; if (key == Attr.CANCELLED) return s == Operators.cancelledSubscription(); if (key == Attr.BUFFERED) return queue != null ? queue.size() : 0; - if (key == Attr.TERMINATED) return done && (queue == null || queue.isEmpty()); + if (key == Attr.TERMINATED) return done && s != Operators.cancelledSubscription(); if (key == Attr.PREFETCH) return prefetch; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; Index: 3rdParty_sources/reactor/reactor/core/publisher/GroupedLiftFuseable.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/GroupedLiftFuseable.java (.../GroupedLiftFuseable.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/GroupedLiftFuseable.java (.../GroupedLiftFuseable.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -83,12 +83,12 @@ Objects.requireNonNull(input, "Lifted subscriber MUST NOT be null"); - if (actual instanceof Fuseable.QueueSubscription + if (actual instanceof QueueSubscription && !(input instanceof QueueSubscription)) { //user didn't produce a QueueSubscription, original was one input = new FluxHide.SuppressFuseableSubscriber<>(input); } //otherwise QS is not required or user already made a compatible conversion source.subscribe(input); } -} +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/Hooks.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/Hooks.java (.../Hooks.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/Hooks.java (.../Hooks.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,10 +27,12 @@ import reactor.core.Exceptions; import reactor.core.publisher.FluxOnAssembly.AssemblySnapshot; import reactor.core.publisher.FluxOnAssembly.MethodReturnSnapshot; +import reactor.core.scheduler.Schedulers; import reactor.util.Logger; import reactor.util.Loggers; import reactor.util.annotation.Nullable; import reactor.util.context.Context; +import reactor.util.context.ContextView; /** * A set of overridable lifecycle hooks that can be used for cross-cutting @@ -472,7 +474,7 @@ /** * Reset global data dropped strategy to throwing via {@link - * reactor.core.Exceptions#failWithCancel()} + * Exceptions#failWithCancel()} */ public static void resetOnNextDropped() { log.debug("Reset to factory defaults : onNextDropped"); @@ -512,6 +514,53 @@ DETECT_CONTEXT_LOSS = false; } + private static final String CONTEXT_IN_THREAD_LOCALS_KEY = "CONTEXT_IN_THREAD_LOCALS"; + + /** + * Globally enables automatic context propagation to {@link ThreadLocal}s. + *

      + * It requires the + * context-propagation library + * to be on the classpath to have an effect. + * Using the implicit global {@code ContextRegistry} it reads entries present in + * the modified {@link Context} using + * {@link Flux#contextWrite(ContextView)} (or {@link Mono#contextWrite(ContextView)}) + * and {@link Flux#contextWrite(Function)} (or {@link Mono#contextWrite(Function)}) + * and restores all {@link ThreadLocal}s associated via same keys for which + * {@code ThreadLocalAccessor}s are registered. + *

      + * The {@link ThreadLocal}s are present in the upstream operators from the + * {@code contextWrite(...)} call and the unmodified (downstream) {@link Context} is + * used when signals are delivered downstream, making the {@code contextWrite(...)} + * a logical boundary for the context propagation mechanism. + *

      + * This mechanism automatically performs {@link Flux#contextCapture()} + * and {@link Mono#contextCapture()} in {@link Flux#blockFirst()}, + * {@link Flux#blockLast()}, {@link Flux#toIterable()}, and {@link Mono#block()} (and + * their overloads). + * @since 3.5.3 + */ + public static void enableAutomaticContextPropagation() { + if (ContextPropagationSupport.isContextPropagationOnClasspath) { + Schedulers.onScheduleHook(CONTEXT_IN_THREAD_LOCALS_KEY, + ContextPropagation.scopePassingOnScheduleHook()); + ContextPropagationSupport.propagateContextToThreadLocals = true; + ContextPropagation.configureContextSnapshotFactory(true); + } + } + + /** + * Globally disables automatic context propagation to {@link ThreadLocal}s. + * @see #enableAutomaticContextPropagation() + */ + public static void disableAutomaticContextPropagation() { + if (ContextPropagationSupport.isContextPropagationOnClasspath) { + Hooks.removeQueueWrapper(CONTEXT_IN_THREAD_LOCALS_KEY); + Schedulers.resetOnScheduleHook(CONTEXT_IN_THREAD_LOCALS_KEY); + ContextPropagationSupport.propagateContextToThreadLocals = false; + } + } + @Nullable @SuppressWarnings("unchecked") static Function createOrUpdateOpHook(Collection, ? extends Publisher>> hooks) { @@ -752,4 +801,4 @@ public static Queue wrapQueue(Queue queue) { return (Queue) QUEUE_WRAPPER.apply(queue); } -} +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/InnerProducer.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/InnerProducer.java (.../InnerProducer.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/InnerProducer.java (.../InnerProducer.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -25,7 +25,7 @@ /** * - * {@link InnerProducer} is a {@link reactor.core.Scannable} {@link Subscription} that produces + * {@link InnerProducer} is a {@link Scannable} {@link Subscription} that produces * data to an {@link #actual()} {@link Subscriber} * * @param output operator produced type @@ -46,4 +46,4 @@ return null; } -} +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/InternalConnectableFluxOperator.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/InternalConnectableFluxOperator.java (.../InternalConnectableFluxOperator.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/InternalConnectableFluxOperator.java (.../InternalConnectableFluxOperator.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -87,9 +87,9 @@ @Override @Nullable - public Object scanUnsafe(Scannable.Attr key) { - if (key == Scannable.Attr.PREFETCH) return getPrefetch(); - if (key == Scannable.Attr.PARENT) return source; + public Object scanUnsafe(Attr key) { + if (key == Attr.PREFETCH) return getPrefetch(); + if (key == Attr.PARENT) return source; return null; } -} +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/Mono.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/Mono.java (.../Mono.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/Mono.java (.../Mono.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,7 @@ import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.BiConsumer; @@ -52,9 +53,9 @@ import reactor.core.Exceptions; import reactor.core.Fuseable; import reactor.core.Scannable; +import reactor.core.publisher.FluxOnAssembly.AssemblySnapshot; import reactor.core.publisher.FluxOnAssembly.CheckpointHeavySnapshot; import reactor.core.publisher.FluxOnAssembly.CheckpointLightSnapshot; -import reactor.core.publisher.FluxOnAssembly.AssemblySnapshot; import reactor.core.scheduler.Scheduler; import reactor.core.scheduler.Scheduler.Worker; import reactor.core.scheduler.Schedulers; @@ -72,6 +73,8 @@ import reactor.util.function.Tuple7; import reactor.util.function.Tuple8; import reactor.util.function.Tuples; +import reactor.core.observability.SignalListener; +import reactor.core.observability.SignalListenerFactory; import reactor.util.retry.Retry; /** @@ -219,25 +222,6 @@ * Create a {@link Mono} provider that will {@link Function#apply supply} a target {@link Mono} * to subscribe to for each {@link Subscriber} downstream. * This operator behaves the same way as {@link #defer(Supplier)}, - * but accepts a {@link Function} that will receive the current {@link Context} as an argument. - * - *

      - * - *

      - * @param contextualMonoFactory a {@link Mono} factory - * @param the element type of the returned Mono instance - * @return a deferred {@link Mono} deriving actual {@link Mono} from context values for each subscription - * @deprecated use {@link #deferContextual(Function)} instead. to be removed in 3.5.0. - */ - @Deprecated - public static Mono deferWithContext(Function> contextualMonoFactory) { - return deferContextual(view -> contextualMonoFactory.apply(Context.of(view))); - } - - /** - * Create a {@link Mono} provider that will {@link Function#apply supply} a target {@link Mono} - * to subscribe to for each {@link Subscriber} downstream. - * This operator behaves the same way as {@link #defer(Supplier)}, * but accepts a {@link Function} that will receive the current {@link ContextView} as an argument. * *

      @@ -537,39 +521,37 @@ *

      * *

      - * Note that the completion stage is not cancelled when that Mono is cancelled, but - * that behavior can be obtained by using {@link #doFinally(Consumer)} that checks - * for a {@link SignalType#CANCEL} and calls eg. - * {@link CompletionStage#toCompletableFuture() .toCompletableFuture().cancel(false)}. + * If the completionStage is also a {@link Future}, cancelling the Mono will cancel the future. + * Use {@link #fromFuture(CompletableFuture, boolean)} with {@code suppressCancellation} set to + * {@code true} if you need to suppress cancellation propagation. * * @param completionStage {@link CompletionStage} that will produce a value (or a null to * complete immediately) * @param type of the expected value * @return A {@link Mono}. */ public static Mono fromCompletionStage(CompletionStage completionStage) { - return onAssembly(new MonoCompletionStage<>(completionStage)); + return onAssembly(new MonoCompletionStage<>(completionStage, false)); } /** - * Create a {@link Mono} that wraps a {@link CompletionStage} on subscription, + * Create a {@link Mono} that wraps a lazily-supplied {@link CompletionStage} on subscription, * emitting the value produced by the {@link CompletionStage}. * *

      * *

      - * Note that the completion stage is not cancelled when that Mono is cancelled, but - * that behavior can be obtained by using {@link #doFinally(Consumer)} that checks - * for a {@link SignalType#CANCEL} and calls eg. - * {@link CompletionStage#toCompletableFuture() .toCompletableFuture().cancel(false)}. + * If the completionStage is also a {@link Future}, cancelling the Mono will cancel the future. + * Use {@link #fromFuture(CompletableFuture, boolean)} with {@code suppressCancellation} set to + * {@code true} if you need to suppress cancellation propagation. * * @param stageSupplier The {@link Supplier} of a {@link CompletionStage} that will produce a value (or a null to * complete immediately). This allows lazy triggering of CompletionStage-based APIs. * @param type of the expected value * @return A {@link Mono}. */ public static Mono fromCompletionStage(Supplier> stageSupplier) { - return defer(() -> onAssembly(new MonoCompletionStage<>(stageSupplier.get()))); + return defer(() -> onAssembly(new MonoCompletionStage<>(stageSupplier.get(), false))); } /** @@ -611,14 +593,14 @@ } /** - * Create a {@link Mono}, producing its value using the provided {@link CompletableFuture}. + * Create a {@link Mono}, producing its value using the provided {@link CompletableFuture} + * and cancelling the future if the Mono gets cancelled. * *

      * *

      - * Note that the future is not cancelled when that Mono is cancelled, but that behavior - * can be obtained by using a {@link #doFinally(Consumer)} that checks - * for a {@link SignalType#CANCEL} and calls {@link CompletableFuture#cancel(boolean)}. + * Use {@link #fromFuture(CompletableFuture, boolean)} with {@code suppressCancellation} set to + * {@code true} if you need to suppress cancellation propagation. * * @param future {@link CompletableFuture} that will produce a value (or a null to * complete immediately) @@ -627,31 +609,67 @@ * @see #fromCompletionStage(CompletionStage) fromCompletionStage for a generalization */ public static Mono fromFuture(CompletableFuture future) { - return onAssembly(new MonoCompletionStage<>(future)); + return fromFuture(future, false); } /** - * Create a {@link Mono} that wraps a {@link CompletableFuture} on subscription, - * emitting the value produced by the Future. + * Create a {@link Mono}, producing its value using the provided {@link CompletableFuture} + * and optionally cancelling the future if the Mono gets cancelled (if {@code suppressCancel == false}). * *

      + * + *

      + * + * @param future {@link CompletableFuture} that will produce a value (or a null to complete immediately) + * @param suppressCancel {@code true} to prevent cancellation of the future when the Mono is cancelled, + * {@code false} otherwise (the default) + * @param type of the expected value + * @return A {@link Mono}. + */ + public static Mono fromFuture(CompletableFuture future, boolean suppressCancel) { + return onAssembly(new MonoCompletionStage<>(future, suppressCancel)); + } + + /** + * Create a {@link Mono} that wraps a lazily-supplied {@link CompletableFuture} on subscription, + * emitting the value produced by the future and cancelling the future if the Mono gets cancelled. + * + *

      * *

      - * Note that the future is not cancelled when that Mono is cancelled, but that behavior - * can be obtained by using a {@link #doFinally(Consumer)} that checks - * for a {@link SignalType#CANCEL} and calls {@link CompletableFuture#cancel(boolean)}. * - * @param futureSupplier The {@link Supplier} of a {@link CompletableFuture} that will produce a value (or a null to - * complete immediately). This allows lazy triggering of future-based APIs. + * @param futureSupplier The {@link Supplier} of a {@link CompletableFuture} that will produce a value + * (or a null to complete immediately). This allows lazy triggering of future-based APIs. * @param type of the expected value * @return A {@link Mono}. * @see #fromCompletionStage(Supplier) fromCompletionStage for a generalization */ public static Mono fromFuture(Supplier> futureSupplier) { - return defer(() -> onAssembly(new MonoCompletionStage<>(futureSupplier.get()))); + return fromFuture(futureSupplier, false); } /** + * Create a {@link Mono} that wraps a lazily-supplied {@link CompletableFuture} on subscription, + * emitting the value produced by the future and optionally cancelling the future if the Mono gets cancelled + * (if {@code suppressCancel == false}). + * + *

      + * + *

      + * + * @param futureSupplier The {@link Supplier} of a {@link CompletableFuture} that will produce a value + * (or a null to complete immediately). This allows lazy triggering of future-based APIs. + * @param suppressCancel {@code true} to prevent cancellation of the future when the Mono is cancelled, + * {@code false} otherwise (the default) + * @param type of the expected value + * @return A {@link Mono}. + * @see #fromCompletionStage(Supplier) fromCompletionStage for a generalization + */ + public static Mono fromFuture(Supplier> futureSupplier, boolean suppressCancel) { + return defer(() -> onAssembly(new MonoCompletionStage<>(futureSupplier.get(), suppressCancel))); + } + + /** * Create a {@link Mono} that completes empty once the provided {@link Runnable} has * been executed. * @@ -822,21 +840,6 @@ } /** - * Create a {@link Mono} emitting the {@link Context} available on subscribe. - * If no Context is available, the mono will simply emit the - * {@link Context#empty() empty Context}. - * - * @return a new {@link Mono} emitting current context - * @see #subscribe(CoreSubscriber) - * @deprecated Use {@link #deferContextual(Function)} or {@link #transformDeferredContextual(BiFunction)} to materialize - * the context. To obtain the same Mono of Context, use {@code Mono.deferContextual(Mono::just)}. To be removed in 3.5.0. - */ - @Deprecated - public static Mono subscriberContext() { - return onAssembly(MonoCurrentContext.INSTANCE); - } - - /** * Uses a resource, generated by a supplier for each individual Subscriber, while streaming the value from a * Mono derived from the same resource and makes sure the resource is released if the * sequence terminates or the Subscriber cancels. @@ -1702,7 +1705,9 @@ */ @Nullable public T block() { - BlockingMonoSubscriber subscriber = new BlockingMonoSubscriber<>(); + Context context = ContextPropagationSupport.shouldPropagateContextToThreadLocals() + ? ContextPropagation.contextCaptureToEmpty() : Context.empty(); + BlockingMonoSubscriber subscriber = new BlockingMonoSubscriber<>(context); subscribe((Subscriber) subscriber); return subscriber.blockingGet(); } @@ -1726,7 +1731,9 @@ */ @Nullable public T block(Duration timeout) { - BlockingMonoSubscriber subscriber = new BlockingMonoSubscriber<>(); + Context context = ContextPropagationSupport.shouldPropagateContextToThreadLocals() + ? ContextPropagation.contextCaptureToEmpty() : Context.empty(); + BlockingMonoSubscriber subscriber = new BlockingMonoSubscriber<>(context); subscribe((Subscriber) subscriber); return subscriber.blockingGet(timeout.toNanos(), TimeUnit.NANOSECONDS); } @@ -1747,7 +1754,10 @@ * @return T the result */ public Optional blockOptional() { - BlockingOptionalMonoSubscriber subscriber = new BlockingOptionalMonoSubscriber<>(); + Context context = ContextPropagationSupport.shouldPropagateContextToThreadLocals() + ? ContextPropagation.contextCaptureToEmpty() : Context.empty(); + BlockingOptionalMonoSubscriber subscriber = + new BlockingOptionalMonoSubscriber<>(context); subscribe((Subscriber) subscriber); return subscriber.blockingGet(); } @@ -1772,7 +1782,10 @@ * @return T the result */ public Optional blockOptional(Duration timeout) { - BlockingOptionalMonoSubscriber subscriber = new BlockingOptionalMonoSubscriber<>(); + Context context = ContextPropagationSupport.shouldPropagateContextToThreadLocals() + ? ContextPropagation.contextCaptureToEmpty() : Context.empty(); + BlockingOptionalMonoSubscriber subscriber = + new BlockingOptionalMonoSubscriber<>(context); subscribe((Subscriber) subscriber); return subscriber.blockingGet(timeout.toNanos(), TimeUnit.NANOSECONDS); } @@ -2260,6 +2273,36 @@ } /** + * If context-propagation library + * is on the classpath, this is a convenience shortcut to capture thread local values during the + * subscription phase and put them in the {@link Context} that is visible upstream of this operator. + *

      + * As a result this operator should generally be used as close as possible to the end of + * the chain / subscription point. + *

      + * If the {@link ContextView} visible upstream is not empty, a small subset of operators will automatically + * restore the context snapshot ({@link #handle(BiConsumer) handle}, {@link #tap(SignalListenerFactory) tap}). + * If context-propagation is not available at runtime, this operator simply returns the current {@link Mono} + * instance. + * + * @return a new {@link Flux} where context-propagation API has been used to capture entries and + * inject them into the {@link Context} + * @see #handle(BiConsumer) + * @see #tap(SignalListenerFactory) + */ + public final Mono contextCapture() { + if (!ContextPropagationSupport.isContextPropagationAvailable()) { + return this; + } + if (ContextPropagationSupport.propagateContextToThreadLocals) { + return onAssembly(new MonoContextWriteRestoringThreadLocals<>( + this, ContextPropagation.contextCapture() + )); + } + return onAssembly(new MonoContextWrite<>(this, ContextPropagation.contextCapture())); + } + + /** * Enrich the {@link Context} visible from downstream for the benefit of upstream * operators, by making all values from the provided {@link ContextView} visible on top * of pairs from downstream. @@ -2302,6 +2345,11 @@ * @see Context */ public final Mono contextWrite(Function contextModifier) { + if (ContextPropagationSupport.shouldPropagateContextToThreadLocals()) { + return onAssembly(new MonoContextWriteRestoringThreadLocals<>( + this, contextModifier + )); + } return onAssembly(new MonoContextWrite<>(this, contextModifier)); } @@ -2472,30 +2520,6 @@ } /** - * Add behavior triggered after the {@link Mono} terminates, either by completing downstream successfully or with an error. - * The arguments will be null depending on success, success with data and error: - *

        - *
      • null, null : completed without data
      • - *
      • T, null : completed with data
      • - *
      • null, Throwable : failed with/without data
      • - *
      - * - *

      - * - *

      - * The relevant signal is propagated downstream, then the {@link BiConsumer} is executed. - * - * @param afterSuccessOrError the callback to call after {@link Subscriber#onNext}, {@link Subscriber#onComplete} without preceding {@link Subscriber#onNext} or {@link Subscriber#onError} - * - * @return a new {@link Mono} - * @deprecated prefer using {@link #doAfterTerminate(Runnable)} or {@link #doFinally(Consumer)}. will be removed in 3.5.0 - */ - @Deprecated - public final Mono doAfterSuccessOrError(BiConsumer afterSuccessOrError) { - return doOnTerminalSignal(this, null, null, afterSuccessOrError); - } - - /** * Add behavior (side-effect) triggered after the {@link Mono} terminates, either by * completing downstream successfully or with an error. *

      @@ -2573,9 +2597,6 @@ */ public final Mono doFinally(Consumer onFinally) { Objects.requireNonNull(onFinally, "onFinally"); - if (this instanceof Fuseable) { - return onAssembly(new MonoDoFinallyFuseable<>(this, onFinally)); - } return onAssembly(new MonoDoFinally<>(this, onFinally)); } @@ -2621,7 +2642,7 @@ * @return a {@link Mono} that cleans up matching elements that get discarded upstream of it. */ public final Mono doOnDiscard(final Class type, final Consumer discardHook) { - return subscriberContext(Operators.discardLocalAdapter(type, discardHook)); + return contextWrite(Operators.discardLocalAdapter(type, discardHook)); } /** @@ -2812,32 +2833,6 @@ } /** - * Add behavior triggered when the {@link Mono} terminates, either by emitting a value, - * completing empty or failing with an error. - * The value passed to the {@link Consumer} reflects the type of completion: - *

        - *
      • null, null : completing without data. handler is executed right before onComplete is propagated downstream
      • - *
      • T, null : completing with data. handler is executed right before onNext is propagated downstream
      • - *
      • null, Throwable : failing. handler is executed right before onError is propagated downstream
      • - *
      - * - *

      - * - *

      - * The {@link BiConsumer} is executed before propagating either onNext, onComplete or onError downstream. - * - * @param onSuccessOrError the callback to call {@link Subscriber#onNext}, {@link Subscriber#onComplete} without preceding {@link Subscriber#onNext} or {@link Subscriber#onError} - * - * @return a new {@link Mono} - * @deprecated prefer using {@link #doOnNext(Consumer)}, {@link #doOnError(Consumer)}, {@link #doOnTerminate(Runnable)} or {@link #doOnSuccess(Consumer)}. will be removed in 3.5.0 - */ - @Deprecated - public final Mono doOnSuccessOrError(BiConsumer onSuccessOrError) { - Objects.requireNonNull(onSuccessOrError, "onSuccessOrError"); - return doOnTerminalSignal(this, v -> onSuccessOrError.accept(v, null), e -> onSuccessOrError.accept(null, e), null); - } - - /** * Add behavior triggered when the {@link Mono} terminates, either by completing with a value, * completing empty or failing with an error. Unlike in {@link Flux#doOnTerminate(Runnable)}, * the simple fact that a {@link Mono} emits {@link Subscriber#onNext(Object) onNext} implies @@ -2858,7 +2853,7 @@ } /** - * Map this {@link Mono} into {@link reactor.util.function.Tuple2 Tuple2<Long, T>} + * Map this {@link Mono} into {@link Tuple2 Tuple2<Long, T>} * of timemillis and source data. The timemillis corresponds to the elapsed time between * the subscribe and the first next signal, as measured by the {@link Schedulers#parallel() parallel} scheduler. * @@ -2873,7 +2868,7 @@ } /** - * Map this {@link Mono} sequence into {@link reactor.util.function.Tuple2 Tuple2<Long, T>} + * Map this {@link Mono} sequence into {@link Tuple2 Tuple2<Long, T>} * of timemillis and source data. The timemillis corresponds to the elapsed time between the subscribe and the first * next signal, as measured by the provided {@link Scheduler}. * @@ -3205,6 +3200,11 @@ * output sink for each onNext. At most one {@link SynchronousSink#next(Object)} * call must be performed and/or 0 or 1 {@link SynchronousSink#error(Throwable)} or * {@link SynchronousSink#complete()}. + *

      + * When the context-propagation library + * is available at runtime and the downstream {@link ContextView} is not empty, this operator implicitly uses the + * library to restore thread locals around the handler {@link BiConsumer}. Typically, this would be done in conjunction + * with the use of {@link #contextCapture()} operator down the chain. * * @param handler the handling {@link BiConsumer} * @param the transformed type @@ -3478,7 +3478,7 @@ * The name serves as a prefix in the reported metrics names. In case no name has been provided, the default name "reactor" will be applied. *

      * The {@link MeterRegistry} used by reactor can be configured via - * {@link reactor.util.Metrics.MicrometerConfiguration#useRegistry(MeterRegistry)} + * {@link Metrics.MicrometerConfiguration#useRegistry(MeterRegistry)} * prior to using this operator, the default being * {@link io.micrometer.core.instrument.Metrics#globalRegistry}. *

      @@ -3487,7 +3487,10 @@ * * @see #name(String) * @see #tag(String, String) + * @deprecated Prefer using the {@link #tap(SignalListenerFactory)} with the {@link SignalListenerFactory} provided by + * the new reactor-core-micrometer module. To be removed in 3.6.0 at the earliest. */ + @Deprecated public final Mono metrics() { if (!Metrics.isInstrumentationAvailable()) { return this; @@ -3503,7 +3506,8 @@ * Give a name to this sequence, which can be retrieved using {@link Scannable#name()} * as long as this is the first reachable {@link Scannable#parents()}. *

      - * If {@link #metrics()} operator is called later in the chain, this name will be used as a prefix for meters' name. + * The name is typically visible at assembly time by the {@link #tap(SignalListenerFactory)} operator, + * which could for example be configured with a metrics listener using the name as a prefix for meters' id. * * @param name a name for the sequence * @@ -3556,6 +3560,52 @@ } /** + * Simply complete the sequence by replacing an {@link Subscriber#onError(Throwable) onError signal} + * with an {@link Subscriber#onComplete() onComplete signal}. All other signals are propagated as-is. + * + *

      + * + * + * @return a new {@link Mono} falling back on completion when an onError occurs + * @see #onErrorReturn(Object) + */ + public final Mono onErrorComplete() { + return onAssembly(new MonoOnErrorReturn<>(this, null, null)); + } + + /** + * Simply complete the sequence by replacing an {@link Subscriber#onError(Throwable) onError signal} + * with an {@link Subscriber#onComplete() onComplete signal} if the error matches the given + * {@link Class}. All other signals, including non-matching onError, are propagated as-is. + * + *

      + * + * + * @return a new {@link Mono} falling back on completion when a matching error occurs + * @see #onErrorReturn(Class, Object) + */ + public final Mono onErrorComplete(Class type) { + Objects.requireNonNull(type, "type must not be null"); + return onErrorComplete(type::isInstance); + } + + /** + * Simply complete the sequence by replacing an {@link Subscriber#onError(Throwable) onError signal} + * with an {@link Subscriber#onComplete() onComplete signal} if the error matches the given + * {@link Predicate}. All other signals, including non-matching onError, are propagated as-is. + * + *

      + * + * + * @return a new {@link Mono} falling back on completion when a matching error occurs + * @see #onErrorReturn(Predicate, Object) + */ + public final Mono onErrorComplete(Predicate predicate) { + Objects.requireNonNull(predicate, "predicate must not be null"); + return onAssembly(new MonoOnErrorReturn<>(this, predicate, null)); + } + + /** * Let compatible operators upstream recover from errors by dropping the * incriminating element from the sequence and continuing with subsequent elements. * The recovered error and associated value are notified via the provided {@link BiConsumer}. @@ -3591,7 +3641,7 @@ */ public final Mono onErrorContinue(BiConsumer errorConsumer) { BiConsumer genericConsumer = errorConsumer; - return subscriberContext(Context.of( + return contextWrite(Context.of( OnNextFailureStrategy.KEY_ON_NEXT_ERROR_STRATEGY, OnNextFailureStrategy.resume(genericConsumer) )); @@ -3681,7 +3731,7 @@ @SuppressWarnings("unchecked") Predicate genericPredicate = (Predicate) errorPredicate; BiConsumer genericErrorConsumer = errorConsumer; - return subscriberContext(Context.of( + return contextWrite(Context.of( OnNextFailureStrategy.KEY_ON_NEXT_ERROR_STRATEGY, OnNextFailureStrategy.resumeIf(genericPredicate, genericErrorConsumer) )); @@ -3698,7 +3748,7 @@ * was used downstream */ public final Mono onErrorStop() { - return subscriberContext(Context.of( + return contextWrite(Context.of( OnNextFailureStrategy.KEY_ON_NEXT_ERROR_STRATEGY, OnNextFailureStrategy.stop())); } @@ -3815,12 +3865,14 @@ *

      * * - * @param fallback the value to emit if an error occurs + * @param fallbackValue the value to emit if an error occurs * * @return a new falling back {@link Mono} + * @see #onErrorComplete() */ - public final Mono onErrorReturn(final T fallback) { - return onErrorResume(throwable -> just(fallback)); + public final Mono onErrorReturn(final T fallbackValue) { + Objects.requireNonNull(fallbackValue, "fallbackValue must not be null"); + return onAssembly(new MonoOnErrorReturn<>(this, null, fallbackValue)); } /** @@ -3834,9 +3886,12 @@ * @param the error type * * @return a new falling back {@link Mono} + * @see #onErrorComplete(Class) */ public final Mono onErrorReturn(Class type, T fallbackValue) { - return onErrorResume(type, throwable -> just(fallbackValue)); + Objects.requireNonNull(type, "type must not be null"); + Objects.requireNonNull(fallbackValue, "fallbackValue must not be null"); + return onErrorReturn(type::isInstance, fallbackValue); } /** @@ -3849,9 +3904,12 @@ * @param fallbackValue the value to emit if an error occurs that matches the predicate * * @return a new {@link Mono} + * @see #onErrorComplete(Predicate) */ public final Mono onErrorReturn(Predicate predicate, T fallbackValue) { - return onErrorResume(predicate, throwable -> just(fallbackValue)); + Objects.requireNonNull(predicate, "predicate must not be null"); + Objects.requireNonNull(fallbackValue, "fallbackValue must not be null"); + return onAssembly(new MonoOnErrorReturn<>(this, predicate, fallbackValue)); } /** @@ -4057,9 +4115,11 @@ return repeatFactory.apply(o.index().map(Tuple2::getT1)); } else { - return repeatFactory.apply(o.index().map(Tuple2::getT1) - .take(maxRepeat) - .concatWith(Flux.error(() -> new IllegalStateException("Exceeded maximum number of repeats")))); + return repeatFactory.apply(o + .index() + .map(Tuple2::getT1) + .take(maxRepeat, false) + .concatWith(Flux.error(() -> new IllegalStateException("Exceeded maximum number of repeats")))); } }).next()); } @@ -4097,7 +4157,7 @@ * The companion is generated by the provided {@link Retry} instance, see {@link Retry#max(long)}, {@link Retry#maxInARow(long)} * and {@link Retry#backoff(long, Duration)} for readily available strategy builders. *

      - * The operator generates a base for the companion, a {@link Flux} of {@link reactor.util.retry.Retry.RetrySignal} + * The operator generates a base for the companion, a {@link Flux} of {@link Retry.RetrySignal} * which each give metadata about each retryable failure whenever this {@link Mono} signals an error. The final companion * should be derived from that base companion and emit data in response to incoming onNext (although it can emit less * elements, or delay the emissions). @@ -4107,11 +4167,11 @@ *

      * *

      - * Note that the {@link reactor.util.retry.Retry.RetrySignal} state can be transient and change between each source + * Note that the {@link Retry.RetrySignal} state can be transient and change between each source * {@link org.reactivestreams.Subscriber#onError(Throwable) onError} or * {@link org.reactivestreams.Subscriber#onNext(Object) onNext}. If processed with a delay, * this could lead to the represented state being out of sync with the state at which the retry - * was evaluated. Map it to {@link reactor.util.retry.Retry.RetrySignal#copy()} right away to mediate this. + * was evaluated. Map it to {@link Retry.RetrySignal#copy()} right away to mediate this. *

      * Note that if the companion {@link Publisher} created by the {@code whenFactory} * emits {@link Context} as trigger objects, these {@link Context} will be merged with @@ -4136,7 +4196,7 @@ * * * @param retrySpec the {@link Retry} strategy that will generate the companion {@link Publisher}, - * given a {@link Flux} that signals each onError as a {@link reactor.util.retry.Retry.RetrySignal}. + * given a {@link Flux} that signals each onError as a {@link Retry.RetrySignal}. * * @return a {@link Mono} that retries on onError when a companion {@link Publisher} produces an onNext signal * @see Retry#max(long) @@ -4171,7 +4231,7 @@ /** * Expect exactly one item from this {@link Mono} source or signal - * {@link java.util.NoSuchElementException} for an empty source. + * {@link NoSuchElementException} for an empty source. *

      * *

      @@ -4206,6 +4266,37 @@ } /** + * Wrap the item produced by this {@link Mono} source into an Optional + * or emit an empty Optional for an empty source. + *

      + * + *

      + * + * @return a {@link Mono} with an Optional containing the item, an empty optional or an error signal + */ + public final Mono> singleOptional() { + if (this instanceof Callable) { + if (this instanceof Fuseable.ScalarCallable) { + @SuppressWarnings("unchecked") + Fuseable.ScalarCallable scalarCallable = (Fuseable.ScalarCallable) this; + + T v; + try { + v = scalarCallable.call(); + } + catch (Exception e) { + return Mono.error(Exceptions.unwrap(e)); + } + return Mono.just(Optional.ofNullable(v)); + } + @SuppressWarnings("unchecked") + Callable thiz = (Callable)this; + return Mono.onAssembly(new MonoSingleOptionalCallable<>(thiz)); + } + return Mono.onAssembly(new MonoSingleOptional<>(this)); + } + + /** * Subscribe to this {@link Mono} and request unbounded demand. *

      * This version doesn't specify any consumption behavior for the events from the @@ -4236,7 +4327,7 @@ * Subscribe a {@link Consumer} to this {@link Mono} that will consume all the * sequence. It will request an unbounded demand ({@code Long.MAX_VALUE}). *

      - * For a passive version that observe and forward incoming data see {@link #doOnNext(java.util.function.Consumer)}. + * For a passive version that observe and forward incoming data see {@link #doOnNext(Consumer)}. *

      * Keep in mind that since the sequence can be asynchronous, this will immediately * return control to the calling thread. This can give the impression the consumer is @@ -4260,7 +4351,7 @@ * The subscription will request an unbounded demand ({@code Long.MAX_VALUE}). *

      * For a passive version that observe and forward incoming data see {@link #doOnSuccess(Consumer)} and - * {@link #doOnError(java.util.function.Consumer)}. + * {@link #doOnError(Consumer)}. *

      * Keep in mind that since the sequence can be asynchronous, this will immediately * return control to the calling thread. This can give the impression the consumer is @@ -4285,7 +4376,7 @@ * will request unbounded demand ({@code Long.MAX_VALUE}). *

      * For a passive version that observe and forward incoming data see {@link #doOnSuccess(Consumer)} and - * {@link #doOnError(java.util.function.Consumer)}. + * {@link #doOnError(Consumer)}. *

      * Keep in mind that since the sequence can be asynchronous, this will immediately * return control to the calling thread. This can give the impression the consumer is @@ -4315,7 +4406,7 @@ * {@code Long.MAX_VALUE} if no such consumer is provided. *

      * For a passive version that observe and forward incoming data see {@link #doOnSuccess(Consumer)} and - * {@link #doOnError(java.util.function.Consumer)}. + * {@link #doOnError(Consumer)}. *

      * Keep in mind that since the sequence can be asynchronous, this will immediately * return control to the calling thread. This can give the impression the consumer is @@ -4347,7 +4438,7 @@ * is tied to the subscription. At subscription, an unbounded request is implicitly made. *

      * For a passive version that observe and forward incoming data see {@link #doOnSuccess(Consumer)} and - * {@link #doOnError(java.util.function.Consumer)}. + * {@link #doOnError(Consumer)}. *

      * Keep in mind that since the sequence can be asynchronous, this will immediately * return control to the calling thread. This can give the impression the consumer is @@ -4378,6 +4469,10 @@ CorePublisher publisher = Operators.onLastAssembly(this); CoreSubscriber subscriber = Operators.toCoreSubscriber(actual); + if (subscriber instanceof Fuseable.QueueSubscription && this != publisher && this instanceof Fuseable && !(publisher instanceof Fuseable)) { + subscriber = new FluxHide.SuppressFuseableSubscriber<>(subscriber); + } + try { if (publisher instanceof OptimizableOperator) { OptimizableOperator operator = (OptimizableOperator) publisher; @@ -4418,55 +4513,6 @@ public abstract void subscribe(CoreSubscriber actual); /** - * Enrich a potentially empty downstream {@link Context} by adding all values - * from the given {@link Context}, producing a new {@link Context} that is propagated - * upstream. - *

      - * The {@link Context} propagation happens once per subscription (not on each onNext): - * it is done during the {@code subscribe(Subscriber)} phase, which runs from - * the last operator of a chain towards the first. - *

      - * So this operator enriches a {@link Context} coming from under it in the chain - * (downstream, by default an empty one) and makes the new enriched {@link Context} - * visible to operators above it in the chain. - * - * @param mergeContext the {@link Context} to merge with a previous {@link Context} - * state, returning a new one. - * - * @return a contextualized {@link Mono} - * @see Context - * @deprecated Use {@link #contextWrite(ContextView)} instead. To be removed in 3.5.0. - */ - @Deprecated - public final Mono subscriberContext(Context mergeContext) { - return subscriberContext(c -> c.putAll(mergeContext.readOnly())); - } - - /** - * Enrich a potentially empty downstream {@link Context} by applying a {@link Function} - * to it, producing a new {@link Context} that is propagated upstream. - *

      - * The {@link Context} propagation happens once per subscription (not on each onNext): - * it is done during the {@code subscribe(Subscriber)} phase, which runs from - * the last operator of a chain towards the first. - *

      - * So this operator enriches a {@link Context} coming from under it in the chain - * (downstream, by default an empty one) and makes the new enriched {@link Context} - * visible to operators above it in the chain. - * - * @param doOnContext the function taking a previous {@link Context} state - * and returning a new one. - * - * @return a contextualized {@link Mono} - * @see Context - * @deprecated Use {@link #contextWrite(Function)} instead. To be removed in 3.5.0. - */ - @Deprecated - public final Mono subscriberContext(Function doOnContext) { - return new MonoContextWrite<>(this, doOnContext); - } - - /** * Run subscribe, onSubscribe and request on a specified {@link Scheduler}'s {@link Worker}. * As such, placing this operator anywhere in the chain will also impact the execution * context of onNext/onError/onComplete signals from the beginning of the chain up to @@ -4504,7 +4550,7 @@ /** * Subscribe the given {@link Subscriber} to this {@link Mono} and return said - * {@link Subscriber} (eg. a {@link MonoProcessor}). + * {@link Subscriber}, allowing subclasses with a richer API to be used fluently. * * @param subscriber the {@link Subscriber} to subscribe with * @param the reified type of the {@link Subscriber} for chaining @@ -4534,11 +4580,10 @@ /** * Tag this mono with a key/value pair. These can be retrieved as a {@link Set} of * all tags throughout the publisher chain by using {@link Scannable#tags()} (as - * traversed - * by {@link Scannable#parents()}). + * traversed by {@link Scannable#parents()}). *

      - * Note that some monitoring systems like Prometheus require to have the exact same set of - * tags for each meter bearing the same name. + * The name is typically visible at assembly time by the {@link #tap(SignalListenerFactory)} operator, + * which could for example be configured with a metrics listener applying the tag(s) to its meters. * * @param key a tag key * @param value a tag value @@ -4607,6 +4652,114 @@ } /** + * Tap into Reactive Streams signals emitted or received by this {@link Mono} and notify a stateful per-{@link Subscriber} + * {@link SignalListener}. + *

      + * Any exception thrown by the {@link SignalListener} methods causes the subscription to be cancelled + * and the subscriber to be terminated with an {@link Subscriber#onError(Throwable) onError signal} of that + * exception. Note that {@link SignalListener#doFinally(SignalType)}, {@link SignalListener#doAfterComplete()} and + * {@link SignalListener#doAfterError(Throwable)} instead just {@link Operators#onErrorDropped(Throwable, Context) drop} + * the exception. + *

      + * This simplified variant assumes the state is purely initialized within the {@link Supplier}, + * as it is called for each incoming {@link Subscriber} without additional context. + *

      + * When the context-propagation library + * is available at runtime and the downstream {@link ContextView} is not empty, this operator implicitly uses the library + * to restore thread locals around all invocations of {@link SignalListener} methods. Typically, this would be done + * in conjunction with the use of {@link #contextCapture()} operator down the chain. + * + * @param simpleListenerGenerator the {@link Supplier} to create a new {@link SignalListener} on each subscription + * @return a new {@link Mono} with side effects defined by generated {@link SignalListener} + * @see #tap(Function) + * @see #tap(SignalListenerFactory) + */ + public final Mono tap(Supplier> simpleListenerGenerator) { + return tap(new SignalListenerFactory() { + @Override + public Void initializePublisherState(Publisher ignored) { + return null; + } + + @Override + public SignalListener createListener(Publisher ignored1, ContextView ignored2, Void ignored3) { + return simpleListenerGenerator.get(); + } + }); + } + + /** + * Tap into Reactive Streams signals emitted or received by this {@link Mono} and notify a stateful per-{@link Subscriber} + * {@link SignalListener}. + *

      + * Any exception thrown by the {@link SignalListener} methods causes the subscription to be cancelled + * and the subscriber to be terminated with an {@link Subscriber#onError(Throwable) onError signal} of that + * exception. Note that {@link SignalListener#doFinally(SignalType)}, {@link SignalListener#doAfterComplete()} and + * {@link SignalListener#doAfterError(Throwable)} instead just {@link Operators#onErrorDropped(Throwable, Context) drop} + * the exception. + *

      + * This simplified variant allows the {@link SignalListener} to be constructed for each subscription + * with access to the incoming {@link Subscriber}'s {@link ContextView}. + *

      + * When the context-propagation library + * is available at runtime and the {@link ContextView} is not empty, this operator implicitly uses the library + * to restore thread locals around all invocations of {@link SignalListener} methods. Typically, this would be done + * in conjunction with the use of {@link #contextCapture()} operator down the chain. + * + * @param listenerGenerator the {@link Function} to create a new {@link SignalListener} on each subscription + * @return a new {@link Mono} with side effects defined by generated {@link SignalListener} + * @see #tap(Supplier) + * @see #tap(SignalListenerFactory) + */ + public final Mono tap(Function> listenerGenerator) { + return tap(new SignalListenerFactory() { + @Override + public Void initializePublisherState(Publisher ignored) { + return null; + } + + @Override + public SignalListener createListener(Publisher ignored1, ContextView listenerContext, Void ignored2) { + return listenerGenerator.apply(listenerContext); + } + }); + } + + /** + * Tap into Reactive Streams signals emitted or received by this {@link Mono} and notify a stateful per-{@link Subscriber} + * {@link SignalListener} created by the provided {@link SignalListenerFactory}. + *

      + * The factory will initialize a {@link SignalListenerFactory#initializePublisherState(Publisher) state object} for + * each {@link Flux} or {@link Mono} instance it is used with, and that state will be cached and exposed for each + * incoming {@link Subscriber} in order to generate the associated {@link SignalListenerFactory#createListener(Publisher, ContextView, Object) listener}. + *

      + * Any exception thrown by the {@link SignalListener} methods causes the subscription to be cancelled + * and the subscriber to be terminated with an {@link Subscriber#onError(Throwable) onError signal} of that + * exception. Note that {@link SignalListener#doFinally(SignalType)}, {@link SignalListener#doAfterComplete()} and + * {@link SignalListener#doAfterError(Throwable)} instead just {@link Operators#onErrorDropped(Throwable, Context) drop} + * the exception. + *

      + * When the context-propagation library + * is available at runtime and the downstream {@link ContextView} is not empty, this operator implicitly uses the library + * to restore thread locals around all invocations of {@link SignalListener} methods. Typically, this would be done + * in conjunction with the use of {@link #contextCapture()} operator down the chain. + * + * @param listenerFactory the {@link SignalListenerFactory} to create a new {@link SignalListener} on each subscription + * @return a new {@link Mono} with side effects defined by generated {@link SignalListener} + * @see #tap(Supplier) + * @see #tap(Function) + */ + public final Mono tap(SignalListenerFactory listenerFactory) { + if (ContextPropagationSupport.shouldPropagateContextToThreadLocals()) { + return onAssembly(new MonoTapRestoringThreadLocals<>(this, listenerFactory)); + } + if (this instanceof Fuseable) { + return onAssembly(new MonoTapFuseable<>(this, listenerFactory)); + } + return onAssembly(new MonoTap<>(this, listenerFactory)); + } + + /** * Return a {@code Mono} which only replays complete and error signals * from this {@link Mono}. * @@ -4871,7 +5024,7 @@ } /** - * If this {@link Mono} is valued, emit a {@link reactor.util.function.Tuple2} pair of + * If this {@link Mono} is valued, emit a {@link Tuple2} pair of * T1 the current clock time in millis (as a {@link Long} measured by the * {@link Schedulers#parallel() parallel} Scheduler) and T2 the emitted data (as a {@code T}). * @@ -4886,7 +5039,7 @@ } /** - * If this {@link Mono} is valued, emit a {@link reactor.util.function.Tuple2} pair of + * If this {@link Mono} is valued, emit a {@link Tuple2} pair of * T1 the current clock time in millis (as a {@link Long} measured by the * provided {@link Scheduler}) and T2 the emitted data (as a {@code T}). * @@ -4921,26 +5074,6 @@ } /** - * Wrap this {@link Mono} into a {@link MonoProcessor} (turning it hot and allowing to block, - * cancel, as well as many other operations). Note that the {@link MonoProcessor} - * is subscribed to its parent source if any. - * - * @return a {@link MonoProcessor} to use to either retrieve value or cancel the underlying {@link Subscription} - * @deprecated prefer {@link #share()} to share a parent subscription, or use {@link Sinks} - */ - @Deprecated - public final MonoProcessor toProcessor() { - if (this instanceof MonoProcessor) { - return (MonoProcessor) this; - } - else { - NextProcessor result = new NextProcessor<>(this); - result.connect(); - return result; - } - } - - /** * Transform this {@link Mono} in order to generate a target {@link Mono}. Unlike {@link #transformDeferred(Function)}, the * provided function is executed as part of assembly. * @@ -5239,4 +5372,4 @@ return EQUALS_BIPREDICATE; } static final BiPredicate EQUALS_BIPREDICATE = Object::equals; -} +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoAll.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/MonoAll.java (.../MonoAll.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoAll.java (.../MonoAll.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,6 @@ import java.util.Objects; import java.util.function.Predicate; -import org.reactivestreams.Subscription; import reactor.core.CoreSubscriber; import reactor.core.Fuseable; import reactor.util.annotation.Nullable; @@ -55,11 +54,8 @@ return super.scanUnsafe(key); } - static final class AllSubscriber extends Operators.MonoSubscriber { + static final class AllSubscriber extends Operators.BaseFluxToMonoOperator { final Predicate predicate; - - Subscription s; - boolean done; AllSubscriber(CoreSubscriber actual, Predicate predicate) { @@ -71,32 +67,14 @@ @Nullable public Object scanUnsafe(Attr key) { if (key == Attr.TERMINATED) return done; - if (key == Attr.PARENT) return s; - if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; return super.scanUnsafe(key); } @Override - public void cancel() { - s.cancel(); - super.cancel(); - } - - @Override - public void onSubscribe(Subscription s) { - if (Operators.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); - - s.request(Long.MAX_VALUE); - } - } - - @Override public void onNext(T t) { - if (done) { + Operators.onDiscard(t, this.actual.currentContext()); return; } @@ -113,7 +91,8 @@ done = true; s.cancel(); - complete(false); + this.actual.onNext(false); + this.actual.onComplete(); } } @@ -134,8 +113,13 @@ return; } done = true; - complete(true); + + completePossiblyEmpty(); } + @Override + Boolean accumulatedValue() { + return true; + } } } Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoAny.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/MonoAny.java (.../MonoAny.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoAny.java (.../MonoAny.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,6 @@ import java.util.Objects; import java.util.function.Predicate; -import org.reactivestreams.Subscription; import reactor.core.CoreSubscriber; import reactor.core.Fuseable; import reactor.util.annotation.Nullable; @@ -55,11 +54,11 @@ return super.scanUnsafe(key); } - static final class AnySubscriber extends Operators.MonoSubscriber { + static final class AnySubscriber extends + Operators.BaseFluxToMonoOperator { + final Predicate predicate; - Subscription s; - boolean done; AnySubscriber(CoreSubscriber actual, Predicate predicate) { @@ -71,33 +70,14 @@ @Nullable public Object scanUnsafe(Attr key) { if (key == Attr.TERMINATED) return done; - if (key == Attr.PARENT) return s; - if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; return super.scanUnsafe(key); } @Override - public void cancel() { - s.cancel(); - super.cancel(); - } - - @Override - public void onSubscribe(Subscription s) { - if (Operators.validate(this.s, s)) { - this.s = s; - - actual.onSubscribe(this); - - s.request(Long.MAX_VALUE); - } - } - - @Override public void onNext(T t) { - if (done) { + Operators.onDiscard(t, this.actual.currentContext()); return; } @@ -110,11 +90,13 @@ actual.onError(Operators.onOperatorError(s, e, t, actual.currentContext())); return; } + if (b) { done = true; s.cancel(); - complete(true); + this.actual.onNext(true); + this.actual.onComplete(); } } @@ -135,8 +117,13 @@ return; } done = true; - complete(false); + + completePossiblyEmpty(); } + @Override + Boolean accumulatedValue() { + return false; + } } } Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoCallable.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/MonoCallable.java (.../MonoCallable.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoCallable.java (.../MonoCallable.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,13 +19,14 @@ import java.time.Duration; import java.util.Objects; import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import reactor.core.CoreSubscriber; import reactor.core.Exceptions; import reactor.core.Fuseable; import reactor.util.annotation.Nullable; -/** + /** * Executes a Callable function and emits a single value to each individual Subscriber. *

      * Preferred to {@link java.util.function.Supplier} because the Callable may throw. @@ -44,28 +45,7 @@ @Override public void subscribe(CoreSubscriber actual) { - Operators.MonoSubscriber - sds = new Operators.MonoSubscriber<>(actual); - - actual.onSubscribe(sds); - - if (sds.isCancelled()) { - return; - } - - try { - T t = callable.call(); - if (t == null) { - sds.onComplete(); - } - else { - sds.complete(t); - } - } - catch (Throwable e) { - actual.onError(Operators.onOperatorError(e, actual.currentContext())); - } - + actual.onSubscribe(new MonoCallableSubscription<>(actual, this.callable)); } @Override @@ -97,4 +77,111 @@ if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; return null; } + + static class MonoCallableSubscription + implements InnerProducer, Fuseable, QueueSubscription { + + final CoreSubscriber actual; + final Callable callable; + + boolean done; + + volatile int requestedOnce; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater REQUESTED_ONCE = + AtomicIntegerFieldUpdater.newUpdater(MonoCallableSubscription.class, + "requestedOnce"); + + volatile boolean cancelled; + + MonoCallableSubscription(CoreSubscriber actual, Callable callable) { + this.actual = actual; + this.callable = callable; + } + + @Override + public CoreSubscriber actual() { + return this.actual; + } + + @Override + public T poll() { + if (this.done) { + return null; + } + + this.done = true; + + try { + return this.callable.call(); + } + catch (Throwable e) { + throw Exceptions.propagate(e); + } + } + + @Override + public void request(long n) { + if (this.cancelled) { + return; + } + + if (this.requestedOnce == 1 || !REQUESTED_ONCE.compareAndSet(this, 0 , 1)) { + return; + } + + final CoreSubscriber s = this.actual; + + final T value; + try { + value = this.callable.call(); + } + catch (Exception e) { + if (this.cancelled) { + Operators.onErrorDropped(e, s.currentContext()); + return; + } + + s.onError(e); + return; + } + + + if (this.cancelled) { + Operators.onDiscard(value, s.currentContext()); + return; + } + + if (value != null) { + s.onNext(value); + } + + s.onComplete(); + } + + @Override + public void cancel() { + this.cancelled = true; + } + + @Override + public int requestFusion(int requestedMode) { + return requestedMode & SYNC; + } + + @Override + public int size() { + return this.done ? 0 : 1; + } + + @Override + public boolean isEmpty() { + return this.done; + } + + @Override + public void clear() { + this.done = true; + } + } } Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoCollect.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/MonoCollect.java (.../MonoCollect.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoCollect.java (.../MonoCollect.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,6 @@ import java.util.function.BiConsumer; import java.util.function.Supplier; -import org.reactivestreams.Subscription; import reactor.core.CoreSubscriber; import reactor.core.Fuseable; import reactor.util.annotation.Nullable; @@ -66,14 +65,12 @@ return super.scanUnsafe(key); } - static final class CollectSubscriber extends Operators.MonoSubscriber { + static final class CollectSubscriber extends Operators.BaseFluxToMonoOperator { final BiConsumer action; R container; - Subscription s; - boolean done; CollectSubscriber(CoreSubscriber actual, @@ -87,45 +84,33 @@ @Override @Nullable public Object scanUnsafe(Attr key) { - if (key == Attr.PARENT) return s; if (key == Attr.TERMINATED) return done; - if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); } @Override - public void onSubscribe(Subscription s) { - if (Operators.validate(this.s, s)) { - this.s = s; - - actual.onSubscribe(this); - - s.request(Long.MAX_VALUE); - } - } - - @Override public void onNext(T t) { if (done) { Operators.onNextDropped(t, actual.currentContext()); return; } - R c; - synchronized (this) { - c = container; - if (c != null) { - try { + try { + final R c; + synchronized (this) { + c = container; + if (c != null) { action.accept(c, t); + return; } - catch (Throwable e) { - Context ctx = actual.currentContext(); - Operators.onDiscard(t, ctx); - onError(Operators.onOperatorError(this, e, t, ctx)); - } - return; } + Operators.onDiscard(t, actual.currentContext()); } - Operators.onDiscard(t, actual.currentContext()); + catch (Throwable e) { + Context ctx = actual.currentContext(); + Operators.onDiscard(t, ctx); + onError(Operators.onOperatorError(this.s, e, t, ctx)); + } } @Override @@ -140,6 +125,12 @@ c = container; container = null; } + + if (c == null) { + Operators.onErrorDropped(t, actual.currentContext()); + return; + } + discard(c); actual.onError(t); } @@ -150,48 +141,44 @@ return; } done = true; - R c; + + completePossiblyEmpty(); + } + + @Override + public void cancel() { + super.cancel(); + + final R c; synchronized (this) { c = container; container = null; } + if (c != null) { - complete(c); + discard(c); } } @Override - protected void discard(R v) { + R accumulatedValue() { + final R c; + synchronized (this) { + c = container; + container = null; + } + + return c; + } + + void discard(R v) { if (v instanceof Collection) { Collection c = (Collection) v; Operators.onDiscardMultiple(c, actual.currentContext()); } else { - super.discard(v); + Operators.onDiscard(v, actual.currentContext()); } } - - @Override - public void cancel() { - int state; - R c; - synchronized (this) { - state = STATE.getAndSet(this, CANCELLED); - if (state != CANCELLED) { - s.cancel(); - } - if (state <= HAS_REQUEST_NO_VALUE) { - c = container; - this.value = null; - container = null; - } - else { - c = null; - } - } - if (c != null) { - discard(c); - } - } } } Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoCollectList.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/MonoCollectList.java (.../MonoCollectList.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoCollectList.java (.../MonoCollectList.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,6 @@ import java.util.ArrayList; import java.util.List; -import org.reactivestreams.Subscription; import reactor.core.CoreSubscriber; import reactor.core.Fuseable; import reactor.util.annotation.Nullable; @@ -46,12 +45,10 @@ return super.scanUnsafe(key); } - static final class MonoCollectListSubscriber extends Operators.MonoSubscriber> { + static final class MonoCollectListSubscriber extends Operators.BaseFluxToMonoOperator> { List list; - Subscription s; - boolean done; MonoCollectListSubscriber(CoreSubscriber> actual) { @@ -63,37 +60,28 @@ @Override @Nullable public Object scanUnsafe(Attr key) { - if (key == Attr.PARENT) return s; if (key == Attr.TERMINATED) return done; - if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.CANCELLED) return !done && list == null; + return super.scanUnsafe(key); } @Override - public void onSubscribe(Subscription s) { - if (Operators.validate(this.s, s)) { - this.s = s; - - actual.onSubscribe(this); - - s.request(Long.MAX_VALUE); - } - } - - @Override public void onNext(T t) { if (done) { Operators.onNextDropped(t, actual.currentContext()); return; } - List l; + + final List l; synchronized (this) { l = list; if (l != null) { l.add(t); return; } } + Operators.onDiscard(t, actual.currentContext()); } @@ -104,57 +92,55 @@ return; } done = true; - List l; + + final List l; synchronized (this) { l = list; list = null; } - discard(l); + + if (l == null) { + return; + } + + Operators.onDiscardMultiple(l, actual.currentContext()); + actual.onError(t); } @Override public void onComplete() { - if(done) { + if (done) { return; } done = true; - List l; + + completePossiblyEmpty(); + } + + @Override + public void cancel() { + s.cancel(); + + final List l; synchronized (this) { l = list; list = null; } + if (l != null) { - complete(l); + Operators.onDiscardMultiple(l, actual.currentContext()); } } @Override - protected void discard(List v) { - Operators.onDiscardMultiple(v, actual.currentContext()); - } - - @Override - public void cancel() { - int state; - List l; + List accumulatedValue() { + final List l; synchronized (this) { - state = STATE.getAndSet(this, CANCELLED); - if (state != CANCELLED) { - s.cancel(); - } - if (state <= HAS_REQUEST_NO_VALUE) { - l = list; - this.value = null; - list = null; - } - else { - l = null; - } + l = list; + list = null; } - if (l != null) { - discard(l); - } + return l; } } } Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoCompletionStage.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/MonoCompletionStage.java (.../MonoCompletionStage.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoCompletionStage.java (.../MonoCompletionStage.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,11 +20,15 @@ import java.util.concurrent.CancellationException; import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionStage; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.function.BiFunction; +import io.micrometer.context.ContextSnapshot; import reactor.core.CoreSubscriber; import reactor.core.Exceptions; -import reactor.core.Fuseable; import reactor.core.Scannable; +import reactor.util.annotation.Nullable; import reactor.util.context.Context; /** @@ -36,51 +40,90 @@ * @param the value type */ final class MonoCompletionStage extends Mono - implements Fuseable, Scannable { + implements Scannable { final CompletionStage future; + final boolean suppressCancellation; - MonoCompletionStage(CompletionStage future) { + MonoCompletionStage(CompletionStage future, boolean suppressCancellation) { this.future = Objects.requireNonNull(future, "future"); + this.suppressCancellation = suppressCancellation; } @Override public void subscribe(CoreSubscriber actual) { - Operators.MonoSubscriber - sds = new Operators.MonoSubscriber<>(actual); + if (ContextPropagationSupport.shouldPropagateContextToThreadLocals()) { + actual.onSubscribe( + new MonoCompletionStageRestoringThreadLocalsSubscription<>( + actual, future, suppressCancellation)); + } else { + actual.onSubscribe(new MonoCompletionStageSubscription<>( + actual, future, suppressCancellation)); + } + } - actual.onSubscribe(sds); + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; + return null; + } - if (sds.isCancelled()) { - return; + static class MonoCompletionStageSubscription implements InnerProducer, + BiFunction { + + final CoreSubscriber actual; + final CompletionStage future; + final boolean suppressCancellation; + + volatile int requestedOnce; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater REQUESTED_ONCE = + AtomicIntegerFieldUpdater.newUpdater(MonoCompletionStageSubscription.class, "requestedOnce"); + + volatile boolean cancelled; + + MonoCompletionStageSubscription(CoreSubscriber actual, CompletionStage future, boolean suppressCancellation) { + this.actual = actual; + this.future = future; + this.suppressCancellation = suppressCancellation; } - future.whenComplete((v, e) -> { - if (sds.isCancelled()) { + @Override + public CoreSubscriber actual() { + return this.actual; + } + + @Override + public Void apply(@Nullable T value, @Nullable Throwable e) { + final CoreSubscriber actual = this.actual; + + if (this.cancelled) { //nobody is interested in the Mono anymore, don't risk dropping errors - Context ctx = sds.currentContext(); + final Context ctx = actual.currentContext(); if (e == null || e instanceof CancellationException) { //we discard any potential value and ignore Future cancellations - Operators.onDiscard(v, ctx); + Operators.onDiscard(value, ctx); } else { //we make sure we keep _some_ track of a Future failure AFTER the Mono cancellation Operators.onErrorDropped(e, ctx); //and we discard any potential value just in case both e and v are not null - Operators.onDiscard(v, ctx); + Operators.onDiscard(value, ctx); } - return; + return null; } + try { if (e instanceof CompletionException) { actual.onError(e.getCause()); } else if (e != null) { actual.onError(e); } - else if (v != null) { - sds.complete(v); + else if (value != null) { + actual.onNext(value); + actual.onComplete(); } else { actual.onComplete(); @@ -90,12 +133,140 @@ Operators.onErrorDropped(e1, actual.currentContext()); throw Exceptions.bubble(e1); } - }); + return null; + } + + @Override + public void request(long n) { + if (this.cancelled) { + return; + } + + if (this.requestedOnce == 1 || !REQUESTED_ONCE.compareAndSet(this, 0 , 1)) { + return; + } + + future.handle(this); + } + + @Override + public void cancel() { + this.cancelled = true; + + final CompletionStage future = this.future; + if (!suppressCancellation && future instanceof Future) { + try { + //noinspection unchecked + ((Future) future).cancel(true); + } + catch (Throwable t) { + Operators.onErrorDropped(t, this.actual.currentContext()); + } + } + } } - @Override - public Object scanUnsafe(Attr key) { - if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; - return null; + static class MonoCompletionStageRestoringThreadLocalsSubscription + implements InnerProducer, BiFunction { + + final CoreSubscriber actual; + final CompletionStage future; + final boolean suppressCancellation; + + volatile int requestedOnce; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater REQUESTED_ONCE = + AtomicIntegerFieldUpdater.newUpdater(MonoCompletionStageRestoringThreadLocalsSubscription.class, "requestedOnce"); + + volatile boolean cancelled; + + MonoCompletionStageRestoringThreadLocalsSubscription( + CoreSubscriber actual, + CompletionStage future, + boolean suppressCancellation) { + this.actual = actual; + this.future = future; + this.suppressCancellation = suppressCancellation; + } + + @Override + public CoreSubscriber actual() { + return this.actual; + } + + @Override + public Void apply(@Nullable T value, @Nullable Throwable e) { + final CoreSubscriber actual = this.actual; + + try (ContextSnapshot.Scope ignored = + ContextPropagation.setThreadLocals(actual.currentContext())) { + if (this.cancelled) { + //nobody is interested in the Mono anymore, don't risk dropping errors + final Context ctx = actual.currentContext(); + if (e == null || e instanceof CancellationException) { + //we discard any potential value and ignore Future cancellations + Operators.onDiscard(value, ctx); + } + else { + //we make sure we keep _some_ track of a Future failure AFTER the Mono cancellation + Operators.onErrorDropped(e, ctx); + //and we discard any potential value just in case both e and v are not null + Operators.onDiscard(value, ctx); + } + + return null; + } + + try { + if (e instanceof CompletionException) { + actual.onError(e.getCause()); + } + else if (e != null) { + actual.onError(e); + } + else if (value != null) { + actual.onNext(value); + actual.onComplete(); + } + else { + actual.onComplete(); + } + } + catch (Throwable e1) { + Operators.onErrorDropped(e1, actual.currentContext()); + throw Exceptions.bubble(e1); + } + return null; + } + } + + @Override + public void request(long n) { + if (this.cancelled) { + return; + } + + if (this.requestedOnce == 1 || !REQUESTED_ONCE.compareAndSet(this, 0 , 1)) { + return; + } + + future.handle(this); + } + + @Override + public void cancel() { + this.cancelled = true; + + final CompletionStage future = this.future; + if (!suppressCancellation && future instanceof Future) { + try { + //noinspection unchecked + ((Future) future).cancel(true); + } + catch (Throwable t) { + Operators.onErrorDropped(t, this.actual.currentContext()); + } + } + } } } Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoContextWriteRestoringThreadLocals.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoContextWriteRestoringThreadLocals.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoContextWriteRestoringThreadLocals.java (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reactor.core.publisher; + +import java.util.Objects; +import java.util.function.Function; + +import io.micrometer.context.ContextSnapshot; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +final class MonoContextWriteRestoringThreadLocals extends MonoOperator { + + final Function doOnContext; + + MonoContextWriteRestoringThreadLocals(Mono source, + Function doOnContext) { + super(source); + this.doOnContext = Objects.requireNonNull(doOnContext, "doOnContext"); + } + + @SuppressWarnings("try") + @Override + public void subscribe(CoreSubscriber actual) { + final Context c = doOnContext.apply(actual.currentContext()); + + try (ContextSnapshot.Scope ignored = ContextPropagation.setThreadLocals(c)) { + source.subscribe(new ContextWriteRestoringThreadLocalsSubscriber<>(actual, c)); + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class ContextWriteRestoringThreadLocalsSubscriber + implements InnerOperator { + + final CoreSubscriber actual; + final Context context; + + Subscription s; + boolean done; + + ContextWriteRestoringThreadLocalsSubscriber(CoreSubscriber actual, Context context) { + this.actual = actual; + this.context = context; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) { + return s; + } + if (key == Attr.RUN_STYLE) { + return Attr.RunStyle.SYNC; + } + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public Context currentContext() { + return this.context; + } + + @SuppressWarnings("try") + @Override + public void onSubscribe(Subscription s) { + // This is needed, as the downstream can then switch threads, + // continue the subscription using different primitives and omit this operator + try (ContextSnapshot.Scope ignored = + ContextPropagation.setThreadLocals(actual.currentContext())) { + if (Operators.validate(this.s, s)) { + this.s = s; + actual.onSubscribe(this); + } + } + } + + @SuppressWarnings("try") + @Override + public void onNext(T t) { + this.done = true; + // We probably ended up here from a request, which set thread locals to + // current context, but we need to clean up and restore thread locals for + // the actual subscriber downstream, as it can expect TLs to match the + // different context. + try (ContextSnapshot.Scope ignored = + ContextPropagation.setThreadLocals(actual.currentContext())) { + actual.onNext(t); + actual.onComplete(); + } + } + + @SuppressWarnings("try") + @Override + public void onError(Throwable t) { + if (this.done) { + Operators.onErrorDropped(t, context); + return; + } + + this.done = true; + + try (ContextSnapshot.Scope ignored = + ContextPropagation.setThreadLocals(actual.currentContext())) { + actual.onError(t); + } + } + + @SuppressWarnings("try") + @Override + public void onComplete() { + if (this.done) { + return; + } + + this.done = true; + + try (ContextSnapshot.Scope ignored = + ContextPropagation.setThreadLocals(actual.currentContext())) { + actual.onComplete(); + } + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @SuppressWarnings("try") + @Override + public void request(long n) { + try (ContextSnapshot.Scope ignored = + ContextPropagation.setThreadLocals(context)) { + s.request(n); + } + } + + @SuppressWarnings("try") + @Override + public void cancel() { + try (ContextSnapshot.Scope ignored = + ContextPropagation.setThreadLocals(context)) { + s.cancel(); + } + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoCount.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/MonoCount.java (.../MonoCount.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoCount.java (.../MonoCount.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,6 @@ package reactor.core.publisher; - -import org.reactivestreams.Subscription; import reactor.core.CoreSubscriber; import reactor.core.Fuseable; import reactor.util.annotation.Nullable; @@ -46,51 +44,43 @@ return super.scanUnsafe(key); } - static final class CountSubscriber extends Operators.MonoSubscriber { + static final class CountSubscriber extends Operators.BaseFluxToMonoOperator { + boolean done; + long counter; - Subscription s; - CountSubscriber(CoreSubscriber actual) { super(actual); } @Override @Nullable public Object scanUnsafe(Attr key) { - if (key == Attr.PARENT) return s; - if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.TERMINATED) return done; return super.scanUnsafe(key); } @Override - public void cancel() { - super.cancel(); - s.cancel(); + public void onNext(T t) { + Operators.onDiscard(t, currentContext()); + counter++; } @Override - public void onSubscribe(Subscription s) { - if (Operators.validate(this.s, s)) { - this.s = s; - - actual.onSubscribe(this); - - s.request(Long.MAX_VALUE); - } + public void onError(Throwable t) { + this.actual.onError(t); } @Override - public void onNext(T t) { - counter++; + public void onComplete() { + completePossiblyEmpty(); } @Override - public void onComplete() { - complete(counter); + Long accumulatedValue() { + return counter; } - } } Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoCreate.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/MonoCreate.java (.../MonoCreate.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoCreate.java (.../MonoCreate.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +29,7 @@ import reactor.core.publisher.FluxCreate.SinkDisposable; import reactor.util.annotation.Nullable; import reactor.util.context.Context; +import reactor.util.context.ContextView; /** * Wraps the downstream Subscriber into a single emission object and calls the given @@ -103,11 +104,17 @@ } @Override + @Deprecated public Context currentContext() { return this.actual.currentContext(); } @Override + public ContextView contextView() { + return this.actual.currentContext(); + } + + @Override @Nullable public Object scanUnsafe(Attr key) { if (key == Attr.TERMINATED) { Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoDefer.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/MonoDefer.java (.../MonoDefer.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoDefer.java (.../MonoDefer.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,6 +37,7 @@ } @SuppressWarnings("unchecked") + @Override public void subscribe(CoreSubscriber actual) { Mono p; Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoDelayElement.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/MonoDelayElement.java (.../MonoDelayElement.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoDelayElement.java (.../MonoDelayElement.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,10 +19,13 @@ import java.util.Objects; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import org.reactivestreams.Subscription; import reactor.core.CoreSubscriber; import reactor.core.Disposable; +import reactor.core.Disposables; +import reactor.core.Fuseable; import reactor.core.scheduler.Scheduler; import reactor.util.annotation.Nullable; @@ -33,7 +36,6 @@ * * @see Reactive-Streams-Commons * @author Simon Baslé - * TODO : Review impl */ final class MonoDelayElement extends InternalMonoOperator { @@ -63,20 +65,35 @@ return super.scanUnsafe(key); } - static final class DelayElementSubscriber extends Operators.MonoSubscriber { + static final class DelayElementSubscriber implements InnerOperator, + Fuseable, + Fuseable.QueueSubscription, + Runnable { + static final Disposable CANCELLED = Disposables.disposed(); + static final Disposable TERMINATED = Disposables.disposed(); + + final CoreSubscriber actual; final long delay; final Scheduler scheduler; final TimeUnit unit; Subscription s; + T value; + boolean done; volatile Disposable task; - boolean done; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater TASK = + AtomicReferenceFieldUpdater.newUpdater(DelayElementSubscriber.class, Disposable.class, "task"); - DelayElementSubscriber(CoreSubscriber actual, Scheduler scheduler, - long delay, TimeUnit unit) { - super(actual); + DelayElementSubscriber( + CoreSubscriber actual, + Scheduler scheduler, + long delay, + TimeUnit unit + ) { + this.actual = actual; this.scheduler = scheduler; this.delay = delay; this.unit = unit; @@ -85,23 +102,22 @@ @Override @Nullable public Object scanUnsafe(Attr key) { - if (key == Attr.TERMINATED) return done; + if (key == Attr.TERMINATED) { + final Disposable task = this.task; + return done && (task == TERMINATED || (task == null && value == null)); + } + if (key == Attr.CANCELLED) return task == CANCELLED; + if (key == Attr.PREFETCH) return 0; if (key == Attr.PARENT) return s; if (key == Attr.RUN_ON) return scheduler; if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; - return super.scanUnsafe(key); + return InnerOperator.super.scanUnsafe(key); } @Override - public void cancel() { - super.cancel(); - if (task != null) { - task.dispose(); - } - if (s != Operators.cancelledSubscription()) { - s.cancel(); - } + public CoreSubscriber actual() { + return this.actual; } @Override @@ -110,7 +126,6 @@ this.s = s; actual.onSubscribe(this); - s.request(Long.MAX_VALUE); } } @@ -120,23 +135,106 @@ Operators.onNextDropped(t, actual.currentContext()); return; } + this.done = true; + this.value = t; + try { - this.task = scheduler.schedule(() -> complete(t), delay, unit); + Disposable currentTask = this.task; + if (currentTask == CANCELLED) { + this.value = null; + Operators.onDiscard(t, actual.currentContext()); + return; + } + + final Disposable nextTask = scheduler.schedule(this, delay, unit); + + for (;;) { + currentTask = this.task; + + if (currentTask == CANCELLED) { + nextTask.dispose(); + Operators.onDiscard(t, actual.currentContext()); + return; + } + + if (currentTask == TERMINATED) { + // scheduled task completion happened before this + // just return and do nothing + return; + } + + if (TASK.compareAndSet(this, null, nextTask)) { + return; + } + } } catch (RejectedExecutionException ree) { - actual.onError(Operators.onRejectedExecution(ree, this, null, t, - actual.currentContext())); - return; + this.value = null; + Operators.onDiscard(t, actual.currentContext()); + actual.onError(Operators.onRejectedExecution(ree, this, null, t, actual.currentContext())); } } @Override + public void run() { + for (;;) { + final Disposable currentTask = this.task; + + if (currentTask == CANCELLED) { + return; + } + + if (TASK.compareAndSet(this, currentTask, TERMINATED)) { + break; + } + // we may to repeat since this may race with CAS in the onNext method + } + + final T value = this.value; + this.value = null; + + this.actual.onNext(value); + this.actual.onComplete(); + } + + @Override + public void cancel() { + for (;;) { + final Disposable task = this.task; + if (task == CANCELLED || task == TERMINATED) { + return; + } + + if (TASK.compareAndSet(this, task, CANCELLED)) { + if (task != null) { + task.dispose(); + + final T value = this.value; + this.value = null; + + Operators.onDiscard(value, actual.currentContext()); + return; + } + break; + } + } + + s.cancel(); + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override public void onComplete() { if (done) { return; } this.done = true; + actual.onComplete(); } @@ -147,7 +245,33 @@ return; } this.done = true; + actual.onError(t); } + + @Override + public T poll() { + return null; + } + + @Override + public int requestFusion(int requestedMode) { + return Fuseable.NONE; + } + + @Override + public int size() { + return 0; + } + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public void clear() { + + } } } Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoDoFinally.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/MonoDoFinally.java (.../MonoDoFinally.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoDoFinally.java (.../MonoDoFinally.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -44,7 +44,7 @@ @Override public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { - return FluxDoFinally.createSubscriber(actual, onFinally, false); + return FluxDoFinally.createSubscriber(actual, onFinally); } @Override Fisheye: Tag c4ce08dc0aae7d9da822088a3d5710484f6b0402 refers to a dead (removed) revision in file `3rdParty_sources/reactor/reactor/core/publisher/MonoDoFinallyFuseable.java'. Fisheye: No comparison available. Pass `N' to diff? Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoDoOnEachFuseable.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/MonoDoOnEachFuseable.java (.../MonoDoOnEachFuseable.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoDoOnEachFuseable.java (.../MonoDoOnEachFuseable.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -23,7 +23,7 @@ import reactor.core.Fuseable; /** - * Peek into the lifecycle events and signals of a sequence, {@link reactor.core.Fuseable} + * Peek into the lifecycle events and signals of a sequence, {@link Fuseable} * version of {@link MonoDoOnEach}. * * @param the value type @@ -49,4 +49,4 @@ if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; return super.scanUnsafe(key); } -} +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoElementAt.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/MonoElementAt.java (.../MonoElementAt.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoElementAt.java (.../MonoElementAt.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,6 @@ import java.util.Objects; -import org.reactivestreams.Subscription; import reactor.core.CoreSubscriber; import reactor.core.Fuseable; import reactor.util.annotation.Nullable; @@ -66,20 +65,17 @@ return super.scanUnsafe(key); } - static final class ElementAtSubscriber - extends Operators.MonoSubscriber { + static final class ElementAtSubscriber extends Operators.BaseFluxToMonoOperator { + @Nullable final T defaultValue; long index; final long target; - Subscription s; - boolean done; - ElementAtSubscriber(CoreSubscriber actual, long index, - T defaultValue) { + ElementAtSubscriber(CoreSubscriber actual, long index, @Nullable T defaultValue) { super(actual); this.index = index; this.target = index; @@ -90,36 +86,11 @@ @Nullable public Object scanUnsafe(Attr key) { if (key == Attr.TERMINATED) return done; - if (key == Attr.PARENT) return s; - if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; return super.scanUnsafe(key); } @Override - public void request(long n) { - super.request(n); - if (n > 0L) { - s.request(Long.MAX_VALUE); - } - } - - @Override - public void cancel() { - super.cancel(); - s.cancel(); - } - - @Override - public void onSubscribe(Subscription s) { - if (Operators.validate(this.s, s)) { - this.s = s; - - actual.onSubscribe(this); - } - } - - @Override public void onNext(T t) { if (done) { Operators.onNextDropped(t, actual.currentContext()); @@ -157,8 +128,9 @@ } done = true; - if(defaultValue != null) { - complete(defaultValue); + final T dv = defaultValue; + if (dv != null) { + completePossiblyEmpty(); } else{ long count = target - index; @@ -167,5 +139,10 @@ actual.currentContext())); } } + + @Override + T accumulatedValue() { + return defaultValue; + } } } Fisheye: Tag c4ce08dc0aae7d9da822088a3d5710484f6b0402 refers to a dead (removed) revision in file `3rdParty_sources/reactor/reactor/core/publisher/MonoExtensions.kt'. Fisheye: No comparison available. Pass `N' to diff? Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoFilterWhen.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/MonoFilterWhen.java (.../MonoFilterWhen.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoFilterWhen.java (.../MonoFilterWhen.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2017-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,7 @@ import org.reactivestreams.Subscription; import reactor.core.CoreSubscriber; import reactor.core.Exceptions; +import reactor.core.Fuseable; import reactor.core.Scannable; import reactor.util.annotation.Nullable; import reactor.util.context.Context; @@ -65,60 +66,46 @@ return super.scanUnsafe(key); } - static final class MonoFilterWhenMain extends Operators.MonoSubscriber { + static final class MonoFilterWhenMain implements InnerOperator, + Fuseable, //for constants only + Fuseable.QueueSubscription { - /* Implementation notes on state transitions: - * This subscriber runs through a few possible state transitions, that are - * expressed through the signal methods rather than an explicit state variable, - * as they are simple enough (states suffixed with a * correspond to a terminal - * signal downstream): - * - SUBSCRIPTION -> EMPTY | VALUED | EARLY ERROR - * - EMPTY -> COMPLETE - * - VALUED -> FILTERING | EARLY ERROR - * - EARLY ERROR* - * - FILTERING -> FEMPTY | FERROR | FVALUED - * - FEMPTY -> COMPLETE - * - FERROR* - * - FVALUED -> ON NEXT + COMPLETE | COMPLETE - * - COMPLETE* - */ - final Function> asyncPredicate; + final CoreSubscriber actual; - //this is only touched by onNext and read by onComplete, so no need for volatile - boolean sourceValued; + Subscription s; - Subscription upstream; + boolean done; - volatile FilterWhenInner asyncFilter; - + volatile FilterWhenInner asyncFilter; + @SuppressWarnings("rawtypes") static final AtomicReferenceFieldUpdater ASYNC_FILTER = AtomicReferenceFieldUpdater.newUpdater(MonoFilterWhenMain.class, FilterWhenInner.class, "asyncFilter"); - @SuppressWarnings("ConstantConditions") - static final FilterWhenInner INNER_CANCELLED = new FilterWhenInner(null, false); + @SuppressWarnings({"ConstantConditions", "rawtypes"}) + static final FilterWhenInner INNER_CANCELLED = new FilterWhenInner(null, false, null); + @SuppressWarnings({"ConstantConditions", "rawtypes"}) + static final FilterWhenInner INNER_TERMINATED = new FilterWhenInner(null, false, null); MonoFilterWhenMain(CoreSubscriber actual, Function> asyncPredicate) { - super(actual); + this.actual = actual; this.asyncPredicate = asyncPredicate; } @Override public void onSubscribe(Subscription s) { - if (Operators.validate(upstream, s)) { - upstream = s; - actual.onSubscribe(this); - s.request(Long.MAX_VALUE); + if (Operators.validate(this.s, s)) { + this.s = s; + this.actual.onSubscribe(this); } } @SuppressWarnings("unchecked") @Override public void onNext(T t) { + this.done = true; //we assume the source is a Mono, so only one onNext will ever happen - sourceValued = true; - setValue(t); Publisher p; try { @@ -127,8 +114,8 @@ } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - super.onError(ex); Operators.onDiscard(t, actual.currentContext()); + this.actual.onError(ex); return; } @@ -140,177 +127,253 @@ } catch (Throwable ex) { Exceptions.throwIfFatal(ex); - super.onError(ex); Operators.onDiscard(t, actual.currentContext()); + this.actual.onError(ex); return; } if (u != null && u) { - complete(t); + this.actual.onNext(t); + this.actual.onComplete(); } else { - actual.onComplete(); Operators.onDiscard(t, actual.currentContext()); + actual.onComplete(); } } else { - FilterWhenInner inner = new FilterWhenInner(this, !(p instanceof Mono)); - if (ASYNC_FILTER.compareAndSet(this, null, inner)) { - p.subscribe(inner); - } + FilterWhenInner inner = new FilterWhenInner<>(this, !(p instanceof Mono), t); + p.subscribe(inner); } } @Override public void onComplete() { - if (!sourceValued) { - //there was no value, we can complete empty - super.onComplete(); + if (this.done) { + return; } - //otherwise just wait for the inner filter to apply, rather than complete too soon + + //there was no value, we can complete empty + this.done = true; + this.actual.onComplete(); } - /* implementation note on onError: - * if the source errored, we can propagate that directly since there - * was no chance for an inner subscriber to have been triggered - * (the source being a Mono). So we can just have the parent's behavior - * of calling actual.onError(t) for onError. - */ - @Override - public void cancel() { - if (super.state != CANCELLED) { - super.cancel(); - upstream.cancel(); - cancelInner(); + public void onError(Throwable t) { + if (this.done) { + Operators.onErrorDropped(t, currentContext()); + return; } + + //there was no value, we can complete empty + this.done = true; + + /* implementation note on onError: + * if the source errored, we can propagate that directly since there + * was no chance for an inner subscriber to have been triggered + * (the source being a Mono). So we can just have the parent's behavior + * of calling actual.onError(t) for onError. + */ + this.actual.onError(t); } - void cancelInner() { - FilterWhenInner a = asyncFilter; - if (a != INNER_CANCELLED) { - a = ASYNC_FILTER.getAndSet(this, INNER_CANCELLED); - if (a != null && a != INNER_CANCELLED) { + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + this.s.cancel(); + + final FilterWhenInner a = asyncFilter; + if (a != INNER_CANCELLED && a != INNER_TERMINATED && ASYNC_FILTER.compareAndSet(this, a, INNER_CANCELLED)) { + if (a != null) { a.cancel(); } } } - void innerResult(@Nullable Boolean item) { - if (item != null && item) { - //will reset the value with itself, but using parent's `value` saves a field - complete(this.value); + public boolean trySetInner(FilterWhenInner inner) { + final FilterWhenInner a = this.asyncFilter; + if (a == null && ASYNC_FILTER.compareAndSet(this, null, inner)) { + return true; } - else { - super.onComplete(); - discard(this.value); + Operators.onDiscard(inner.value, currentContext()); + return false; + } + + void innerResult(boolean item, FilterWhenInner inner) { + final FilterWhenInner a = this.asyncFilter; + if (a == inner && ASYNC_FILTER.compareAndSet(this, inner, INNER_TERMINATED)) { + if (item) { + //will reset the value with itself, but using parent's `value` saves a field + this.actual.onNext(inner.value); + this.actual.onComplete(); + } + else { + Operators.onDiscard(inner.value, currentContext()); + this.actual.onComplete(); + } } + // do nothing, value already discarded } - void innerError(Throwable ex) { + void innerError(Throwable ex, FilterWhenInner inner) { //if the inner subscriber (the filter one) errors, then we can //always propagate that error directly, as it means that the source Mono //was at least valued rather than in error. - super.onError(ex); - discard(this.value); + final FilterWhenInner a = this.asyncFilter; + if (a == inner && ASYNC_FILTER.compareAndSet(this, inner, INNER_TERMINATED)) { + Operators.onDiscard(inner.value, currentContext()); + this.actual.onError(ex); + } + + Operators.onErrorDropped(ex, currentContext()); + + // do nothing with value, value already discarded } @Override @Nullable public Object scanUnsafe(Attr key) { - if (key == Attr.PARENT) return upstream; - if (key == Attr.TERMINATED) return asyncFilter != null - ? asyncFilter.scanUnsafe(Attr.TERMINATED) - : super.scanUnsafe(Attr.TERMINATED); - if (key == RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.PARENT) return s; + if (key == Attr.PREFETCH) return 0; + if (key == Attr.TERMINATED) { + final FilterWhenInner af = asyncFilter; + return done && (af == null || af == INNER_TERMINATED); + } + if (key == Attr.CANCELLED) return asyncFilter == INNER_CANCELLED; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; //CANCELLED, PREFETCH - return super.scanUnsafe(key); + return InnerOperator.super.scanUnsafe(key); } @Override public Stream inners() { - FilterWhenInner c = asyncFilter; - return c == null ? Stream.empty() : Stream.of(c); + final FilterWhenInner c = asyncFilter; + return c == null || c == INNER_CANCELLED || c == INNER_TERMINATED ? Stream.empty() : Stream.of(c); } + + @Override + public CoreSubscriber actual() { + return this.actual; + } + + @Override + public T poll() { + return null; + } + + @Override + public int requestFusion(int requestedMode) { + return Fuseable.NONE; + } + + @Override + public int size() { + return 0; + } + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public void clear() { + + } } - static final class FilterWhenInner implements InnerConsumer { + static final class FilterWhenInner implements InnerConsumer { - final MonoFilterWhenMain main; + final MonoFilterWhenMain parent; /** should the filter publisher be cancelled once we received the first value? */ final boolean cancelOnNext; + final T value; + boolean done; - volatile Subscription sub; + Subscription s; - static final AtomicReferenceFieldUpdater SUB = - AtomicReferenceFieldUpdater.newUpdater(FilterWhenInner.class, Subscription.class, "sub"); - - FilterWhenInner(MonoFilterWhenMain main, boolean cancelOnNext) { - this.main = main; + FilterWhenInner(MonoFilterWhenMain parent, boolean cancelOnNext, T value) { + this.parent = parent; this.cancelOnNext = cancelOnNext; + this.value = value; } @Override public void onSubscribe(Subscription s) { - if (Operators.setOnce(SUB, this, s)) { - s.request(Long.MAX_VALUE); + if (Operators.validate(this.s, s)) { + this.s = s; + if (this.parent.trySetInner(this)) { + s.request(Long.MAX_VALUE); + } else { + s.cancel(); + } } } @Override public void onNext(Boolean t) { - if (!done) { - if (cancelOnNext) { - sub.cancel(); - } - done = true; - main.innerResult(t); + if (done) { + return; } + + done = true; + + if (cancelOnNext) { + s.cancel(); + } + + parent.innerResult(t, this); } @Override public void onError(Throwable t) { - if (!done) { - done = true; - main.innerError(t); - } else { - Operators.onErrorDropped(t, main.currentContext()); + if (done) { + Operators.onErrorDropped(t, parent.currentContext()); + return; } - } - @Override - public Context currentContext() { - return main.currentContext(); + done = true; + parent.innerError(t, this); } @Override public void onComplete() { - if (!done) { - //the filter publisher was empty - done = true; - main.innerResult(null); //will trigger actual.onComplete() + if (done) { + return; } + + //the filter publisher was empty + done = true; + parent.innerResult(false, this); //will trigger actual.onComplete() } - void cancel() { - Operators.terminate(SUB, this); + @Override + public Context currentContext() { + return parent.currentContext(); } @Override @Nullable public Object scanUnsafe(Attr key) { - if (key == Attr.PARENT) return sub; - if (key == Attr.ACTUAL) return main; - if (key == Attr.CANCELLED) return sub == Operators.cancelledSubscription(); + if (key == Attr.PARENT) return s; + if (key == Attr.ACTUAL) return parent; if (key == Attr.TERMINATED) return done; - if (key == Attr.PREFETCH) return Integer.MAX_VALUE; + if (key == Attr.PREFETCH) return 0; if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return done ? 0L : 1L; - if (key == RUN_STYLE) return SYNC; + if (key == Attr.RUN_STYLE) return SYNC; return null; } + + void cancel() { + this.s.cancel(); + } } } Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoFlatMap.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/MonoFlatMap.java (.../MonoFlatMap.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoFlatMap.java (.../MonoFlatMap.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -54,10 +54,7 @@ return null; } - FlatMapMain manager = new FlatMapMain<>(actual, mapper); - actual.onSubscribe(manager); - - return manager; + return new FlatMapMain<>(actual, mapper); } @Override @@ -66,26 +63,28 @@ return super.scanUnsafe(key); } - static final class FlatMapMain extends Operators.MonoSubscriber { + static final class FlatMapMain implements InnerOperator, + Fuseable, //for constants only + QueueSubscription { final Function> mapper; - final FlatMapInner second; + final CoreSubscriber actual; boolean done; - volatile Subscription s; + Subscription s; + + volatile FlatMapInner second; + @SuppressWarnings("rawtypes") - static final AtomicReferenceFieldUpdater S = - AtomicReferenceFieldUpdater.newUpdater(FlatMapMain.class, - Subscription.class, - "s"); + static final AtomicReferenceFieldUpdater SECOND = + AtomicReferenceFieldUpdater.newUpdater(FlatMapMain.class, FlatMapInner.class, "second"); - FlatMapMain(CoreSubscriber subscriber, + FlatMapMain(CoreSubscriber actual, Function> mapper) { - super(subscriber); + this.actual = actual; this.mapper = mapper; - this.second = new FlatMapInner<>(this); } @Override @@ -94,20 +93,28 @@ } @Override + public CoreSubscriber actual() { + return this.actual; + } + + @Override @Nullable public Object scanUnsafe(Attr key) { if (key == Attr.PARENT) return s; - if (key == Attr.CANCELLED) return s == Operators.cancelledSubscription(); + if (key == Attr.PREFETCH) return 0; + if (key == Attr.CANCELLED) return second == FlatMapInner.CANCELLED; if (key == Attr.TERMINATED) return done; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return super.scanUnsafe(key); + return InnerOperator.super.scanUnsafe(key); } @Override public void onSubscribe(Subscription s) { - if (Operators.setOnce(S, this, s)) { - s.request(Long.MAX_VALUE); + if (Operators.validate(this.s, s)) { + this.s = s; + + this.actual.onSubscribe(this); } } @@ -148,13 +155,14 @@ actual.onComplete(); } else { - complete(v); + actual.onNext(v); + actual.onComplete(); } return; } try { - m.subscribe(second); + m.subscribe(new FlatMapInner<>(this)); } catch (Throwable e) { actual.onError(Operators.onOperatorError(this, e, t, @@ -182,39 +190,81 @@ } @Override + public void request(long n) { + this.s.request(n); + } + + @Override public void cancel() { - super.cancel(); - Operators.terminate(S, this); - second.cancel(); + this.s.cancel(); + + final FlatMapInner second = this.second; + if (second == FlatMapInner.CANCELLED || !SECOND.compareAndSet(this, second, FlatMapInner.CANCELLED)) { + return; + } + + if (second != null) { + second.s.cancel(); + } } + @Override + public int requestFusion(int requestedMode) { + return Fuseable.NONE; + } + + @Override + public R poll() { + return null; + } + + @Override + public int size() { + return 0; + } + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public void clear() { + + } + + boolean setSecond(FlatMapInner inner) { + return this.second == null && SECOND.compareAndSet(this, null, inner); + } + void secondError(Throwable ex) { actual.onError(ex); } + void secondComplete(R t) { + actual.onNext(t); + actual.onComplete(); + } + void secondComplete() { actual.onComplete(); } } static final class FlatMapInner implements InnerConsumer { + static final FlatMapInner CANCELLED = new FlatMapInner<>(null); + final FlatMapMain parent; - volatile Subscription s; - @SuppressWarnings("rawtypes") - static final AtomicReferenceFieldUpdater S = - AtomicReferenceFieldUpdater.newUpdater(FlatMapInner.class, - Subscription.class, - "s"); + Subscription s; boolean done; FlatMapInner(FlatMapMain parent) { this.parent = parent; } - @Override public Context currentContext() { return parent.currentContext(); @@ -234,8 +284,14 @@ @Override public void onSubscribe(Subscription s) { - if (Operators.setOnce(S, this, s)) { - s.request(Long.MAX_VALUE); + if (Operators.validate(this.s, s)) { + this.s = s; + + if (this.parent.setSecond(this)) { + s.request(Long.MAX_VALUE); + } else { + s.cancel(); + } } } @@ -246,7 +302,7 @@ return; } done = true; - this.parent.complete(t); + this.parent.secondComplete(t); } @Override @@ -267,10 +323,5 @@ done = true; this.parent.secondComplete(); } - - void cancel() { - Operators.terminate(S, this); - } - } } Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoFlattenIterable.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/MonoFlattenIterable.java (.../MonoFlattenIterable.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoFlattenIterable.java (.../MonoFlattenIterable.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,9 +16,9 @@ package reactor.core.publisher; -import java.util.Iterator; import java.util.Objects; import java.util.Queue; +import java.util.Spliterator; import java.util.concurrent.Callable; import java.util.function.Function; import java.util.function.Supplier; @@ -73,10 +73,10 @@ } Iterable iter = mapper.apply(v); - Iterator it = iter.iterator(); - boolean itFinite = FluxIterable.checkFinite(iter); + Spliterator sp = iter.spliterator(); + boolean itFinite = FluxIterable.checkFinite(sp); - FluxIterable.subscribe(actual, it, itFinite); + FluxIterable.subscribe(actual, sp, itFinite); return null; } Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoFromPublisher.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/MonoFromPublisher.java (.../MonoFromPublisher.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoFromPublisher.java (.../MonoFromPublisher.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -55,6 +55,10 @@ @Override @SuppressWarnings("unchecked") public void subscribe(CoreSubscriber actual) { + if (ContextPropagationSupport.shouldPropagateContextToThreadLocals()) { + actual = new MonoSource.MonoSourceRestoringThreadLocalsSubscriber<>(actual); + } + try { CoreSubscriber subscriber = subscribeOrReturn(actual); if (subscriber == null) { @@ -85,13 +89,13 @@ @Override @Nullable - public Object scanUnsafe(Scannable.Attr key) { - if (key == Scannable.Attr.PARENT) { + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) { return source; } - if (key == Scannable.Attr.RUN_STYLE) { + if (key == Attr.RUN_STYLE) { return Attr.RunStyle.SYNC; } return null; } -} +} \ No newline at end of file Fisheye: Tag c4ce08dc0aae7d9da822088a3d5710484f6b0402 refers to a dead (removed) revision in file `3rdParty_sources/reactor/reactor/core/publisher/MonoFunctions.kt'. Fisheye: No comparison available. Pass `N' to diff? Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoHandle.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/MonoHandle.java (.../MonoHandle.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoHandle.java (.../MonoHandle.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,7 +41,9 @@ @Override public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { - return new FluxHandle.HandleSubscriber<>(actual, handler); + BiConsumer> handler2 = ContextPropagationSupport.shouldRestoreThreadLocalsInSomeOperators() ? + ContextPropagation.contextRestoreForHandle(this.handler, actual::currentContext) : this.handler; + return new FluxHandle.HandleSubscriber<>(actual, handler2); } @Override Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoHandleFuseable.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/MonoHandleFuseable.java (.../MonoHandleFuseable.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoHandleFuseable.java (.../MonoHandleFuseable.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,7 +43,9 @@ @Override public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { - return new FluxHandleFuseable.HandleFuseableSubscriber<>(actual, handler); + BiConsumer> handler2 = ContextPropagationSupport.shouldRestoreThreadLocalsInSomeOperators() ? + ContextPropagation.contextRestoreForHandle(this.handler, actual::currentContext) : this.handler; + return new FluxHandleFuseable.HandleFuseableSubscriber<>(actual, handler2); } @Override Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoHasElement.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/MonoHasElement.java (.../MonoHasElement.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoHasElement.java (.../MonoHasElement.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,6 @@ package reactor.core.publisher; - -import org.reactivestreams.Subscription; import reactor.core.CoreSubscriber; import reactor.core.Fuseable; import reactor.util.annotation.Nullable; @@ -34,6 +32,7 @@ @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); } @@ -43,51 +42,63 @@ } static final class HasElementSubscriber - extends Operators.MonoSubscriber { - Subscription s; + extends Operators.BaseFluxToMonoOperator { + boolean done; + HasElementSubscriber(CoreSubscriber actual) { super(actual); } @Override @Nullable public Object scanUnsafe(Attr key) { - if (key == Attr.PARENT) { - return s; - } - if (key == Attr.RUN_STYLE) { - return Attr.RunStyle.SYNC; - } + if (key == Attr.TERMINATED) return done; + return super.scanUnsafe(key); } @Override - public void cancel() { - super.cancel(); - s.cancel(); - } + public void onNext(T t) { + if (done) { + Operators.onNextDropped(t, currentContext()); + return; + } - @Override - public void onSubscribe(Subscription s) { - if (Operators.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + this.done = true; - s.request(Long.MAX_VALUE); - } + Operators.onDiscard(t, currentContext()); + + this.actual.onNext(true); + this.actual.onComplete(); } @Override - public void onNext(T t) { - //here we avoid the cancel because the source is assumed to be a Mono - complete(true); + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, currentContext()); + return; + } + + this.done = true; + + this.actual.onError(t); } @Override public void onComplete() { - complete(false); + if (done) { + return; + } + + this.done = true; + + completePossiblyEmpty(); } + @Override + Boolean accumulatedValue() { + return false; + } } } Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoHasElements.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/MonoHasElements.java (.../MonoHasElements.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoHasElements.java (.../MonoHasElements.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,6 @@ package reactor.core.publisher; -import org.reactivestreams.Subscription; import reactor.core.CoreSubscriber; import reactor.core.Fuseable; import reactor.util.annotation.Nullable; @@ -43,49 +42,60 @@ return super.scanUnsafe(key); } - static final class HasElementsSubscriber extends Operators.MonoSubscriber { - Subscription s; + static final class HasElementsSubscriber extends Operators.BaseFluxToMonoOperator { + boolean done; + HasElementsSubscriber(CoreSubscriber actual) { super(actual); } @Override @Nullable public Object scanUnsafe(Attr key) { - if (key == Attr.PARENT) return s; - if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.TERMINATED) return done; return super.scanUnsafe(key); } @Override - public void cancel() { - super.cancel(); - s.cancel(); - } + public void onNext(T t) { + Operators.onDiscard(t, currentContext()); - @Override - public void onSubscribe(Subscription s) { - if (Operators.validate(this.s, s)) { - this.s = s; - actual.onSubscribe(this); + if (!done) { + s.cancel(); - s.request(Long.MAX_VALUE); + this.done = true; + + this.actual.onNext(true); + this.actual.onComplete(); } } @Override - public void onNext(T t) { - s.cancel(); + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, currentContext()); + return; + } - complete(true); + this.actual.onError(t); } @Override public void onComplete() { - complete(false); + if (done) { + return; + } + + this.done = true; + + completePossiblyEmpty(); } + @Override + Boolean accumulatedValue() { + return false; + } } } Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoIgnorePublisher.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/MonoIgnorePublisher.java (.../MonoIgnorePublisher.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoIgnorePublisher.java (.../MonoIgnorePublisher.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -79,13 +79,13 @@ @Override @Nullable - public Object scanUnsafe(Scannable.Attr key) { - if (key == Scannable.Attr.PARENT) { + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) { return source; } if (key == Attr.RUN_STYLE) { return Attr.RunStyle.SYNC; } return null; } -} +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoIgnoreThen.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/MonoIgnoreThen.java (.../MonoIgnoreThen.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoIgnoreThen.java (.../MonoIgnoreThen.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -219,6 +219,10 @@ if (i == a.length) { Mono m = this.lastMono; if (m instanceof Callable) { + if (isCancelled(this.state)) { + //NB: in the non-callable case, this is handled by activeSubscription.cancel() + return; + } T v; try { v = ((Callable)m).call(); @@ -240,6 +244,10 @@ final Publisher m = a[i]; if (m instanceof Callable) { + if (isCancelled(this.state)) { + //NB: in the non-callable case, this is handled by activeSubscription.cancel() + return; + } try { Operators.onDiscard(((Callable) m).call(), currentContext()); } Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoMetrics.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/MonoMetrics.java (.../MonoMetrics.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoMetrics.java (.../MonoMetrics.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2018-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,10 +20,7 @@ import org.reactivestreams.Subscription; import reactor.core.CoreSubscriber; import reactor.util.Metrics; -import reactor.util.annotation.Nullable; -import static reactor.core.publisher.FluxMetrics.*; - /** * Activate metrics gathering on a {@link Mono}, assumes Micrometer is on the classpath. * @@ -34,6 +31,7 @@ * @author Simon Baslé * @author Stephane Maldini */ +@Deprecated final class MonoMetrics extends InternalMonoOperator { final String name; @@ -44,8 +42,8 @@ MonoMetrics(Mono mono) { super(mono); - this.name = resolveName(mono); - this.tags = resolveTags(mono, DEFAULT_TAGS_MONO); + this.name = FluxMetrics.resolveName(mono); + this.tags = FluxMetrics.resolveTags(mono, FluxMetrics.DEFAULT_TAGS_MONO); this.registryCandidate = Metrics.MicrometerConfiguration.getRegistry(); } @@ -89,7 +87,7 @@ @Override final public void cancel() { - recordCancel(sequenceName, commonTags, registry, subscribeToTerminateSample); + FluxMetrics.recordCancel(sequenceName, commonTags, registry, subscribeToTerminateSample); s.cancel(); } @@ -99,39 +97,39 @@ return; } done = true; - recordOnCompleteEmpty(sequenceName, commonTags, registry, subscribeToTerminateSample); + FluxMetrics.recordOnCompleteEmpty(sequenceName, commonTags, registry, subscribeToTerminateSample); actual.onComplete(); } @Override final public void onError(Throwable e) { if (done) { - recordMalformed(sequenceName, commonTags, registry); + FluxMetrics.recordMalformed(sequenceName, commonTags, registry); Operators.onErrorDropped(e, actual.currentContext()); return; } done = true; - recordOnError(sequenceName, commonTags, registry, subscribeToTerminateSample, e); + FluxMetrics.recordOnError(sequenceName, commonTags, registry, subscribeToTerminateSample, e); actual.onError(e); } @Override public void onNext(T t) { if (done) { - recordMalformed(sequenceName, commonTags, registry); + FluxMetrics.recordMalformed(sequenceName, commonTags, registry); Operators.onNextDropped(t, actual.currentContext()); return; } done = true; - recordOnComplete(sequenceName, commonTags, registry, subscribeToTerminateSample); + FluxMetrics.recordOnComplete(sequenceName, commonTags, registry, subscribeToTerminateSample); actual.onNext(t); actual.onComplete(); } @Override public void onSubscribe(Subscription s) { if (Operators.validate(this.s, s)) { - recordOnSubscribe(sequenceName, commonTags, registry); + FluxMetrics.recordOnSubscribe(sequenceName, commonTags, registry); this.subscribeToTerminateSample = Timer.start(clock); this.s = s; actual.onSubscribe(this); Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoMetricsFuseable.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/MonoMetricsFuseable.java (.../MonoMetricsFuseable.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoMetricsFuseable.java (.../MonoMetricsFuseable.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2018-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,9 +26,6 @@ import reactor.util.Metrics; import reactor.util.annotation.Nullable; -import static reactor.core.publisher.FluxMetrics.resolveName; -import static reactor.core.publisher.FluxMetrics.resolveTags; - /** * Activate metrics gathering on a {@link Mono} (Fuseable version), assumes Micrometer is on the classpath. @@ -38,6 +35,7 @@ * @author Simon Baslé * @author Stephane Maldini */ +@Deprecated final class MonoMetricsFuseable extends InternalMonoOperator implements Fuseable { final String name; @@ -48,8 +46,8 @@ MonoMetricsFuseable(Mono mono) { super(mono); - this.name = resolveName(mono); - this.tags = resolveTags(mono, FluxMetrics.DEFAULT_TAGS_MONO); + this.name = FluxMetrics.resolveName(mono); + this.tags = FluxMetrics.resolveTags(mono, FluxMetrics.DEFAULT_TAGS_MONO); this.registryCandidate = Metrics.MicrometerConfiguration.getRegistry();; } @@ -78,7 +76,7 @@ int mode; @Nullable - Fuseable.QueueSubscription qs; + QueueSubscription qs; MetricsFuseableSubscriber(CoreSubscriber actual, MeterRegistry registry, @@ -198,4 +196,4 @@ } } -} +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoName.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/MonoName.java (.../MonoName.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoName.java (.../MonoName.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,9 +17,9 @@ package reactor.core.publisher; import java.util.Collections; -import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; import java.util.Objects; -import java.util.Set; import reactor.core.CoreSubscriber; import reactor.core.Fuseable; @@ -38,61 +38,68 @@ final String name; - final Set> tags; + final List> tagsWithDuplicates; @SuppressWarnings("unchecked") static Mono createOrAppend(Mono source, String name) { Objects.requireNonNull(name, "name"); if (source instanceof MonoName) { MonoName s = (MonoName) source; - return new MonoName<>(s.source, name, s.tags); + return new MonoName<>(s.source, name, s.tagsWithDuplicates); } if (source instanceof MonoNameFuseable) { MonoNameFuseable s = (MonoNameFuseable) source; - return new MonoNameFuseable<>(s.source, name, s.tags); + return new MonoNameFuseable<>(s.source, name, s.tagsWithDuplicates); } if (source instanceof Fuseable) { return new MonoNameFuseable<>(source, name, null); } return new MonoName<>(source, name, null); } - @SuppressWarnings("unchecked") static Mono createOrAppend(Mono source, String tagName, String tagValue) { Objects.requireNonNull(tagName, "tagName"); Objects.requireNonNull(tagValue, "tagValue"); - Set> tags = Collections.singleton(Tuples.of(tagName, tagValue)); + Tuple2 newTag = Tuples.of(tagName, tagValue); if (source instanceof MonoName) { MonoName s = (MonoName) source; - if(s.tags != null) { - tags = new HashSet<>(tags); - tags.addAll(s.tags); + List> tags; + if(s.tagsWithDuplicates != null) { + tags = new LinkedList<>(s.tagsWithDuplicates); + tags.add(newTag); } + else { + tags = Collections.singletonList(newTag); + } return new MonoName<>(s.source, s.name, tags); } if (source instanceof MonoNameFuseable) { MonoNameFuseable s = (MonoNameFuseable) source; - if (s.tags != null) { - tags = new HashSet<>(tags); - tags.addAll(s.tags); + List> tags; + if (s.tagsWithDuplicates != null) { + tags = new LinkedList<>(s.tagsWithDuplicates); + tags.add(newTag); } + else { + tags = Collections.singletonList(newTag); + } return new MonoNameFuseable<>(s.source, s.name, tags); } if (source instanceof Fuseable) { - return new MonoNameFuseable<>(source, null, tags); + return new MonoNameFuseable<>(source, null, Collections.singletonList(newTag)); } - return new MonoName<>(source, null, tags); + return new MonoName<>(source, null, Collections.singletonList(newTag)); } MonoName(Mono source, @Nullable String name, - @Nullable Set> tags) { + @Nullable List> tags) { super(source); this.name = name; - this.tags = tags; + this.tagsWithDuplicates = tags; } @Override @@ -107,8 +114,8 @@ return name; } - if (key == Attr.TAGS && tags != null) { - return tags.stream(); + if (key == Attr.TAGS && tagsWithDuplicates != null) { + return tagsWithDuplicates.stream(); } if (key == Attr.RUN_STYLE) { Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoNameFuseable.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/MonoNameFuseable.java (.../MonoNameFuseable.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoNameFuseable.java (.../MonoNameFuseable.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,15 +16,14 @@ package reactor.core.publisher; -import java.util.Set; +import java.util.List; import reactor.core.CoreSubscriber; import reactor.core.Fuseable; import reactor.util.annotation.Nullable; import reactor.util.function.Tuple2; import static reactor.core.Scannable.Attr.RUN_STYLE; -import static reactor.core.Scannable.Attr.RunStyle.SYNC; /** * An operator that just bears a name or a set of tags, which can be retrieved via the @@ -37,14 +36,14 @@ final String name; - final Set> tags; + final List> tagsWithDuplicates; MonoNameFuseable(Mono source, @Nullable String name, - @Nullable Set> tags) { + @Nullable List> tags) { super(source); this.name = name; - this.tags = tags; + this.tagsWithDuplicates = tags; } @Override @@ -59,8 +58,8 @@ return name; } - if (key == Attr.TAGS && tags != null) { - return tags.stream(); + if (key == Attr.TAGS && tagsWithDuplicates != null) { + return tagsWithDuplicates.stream(); } if (key == RUN_STYLE) { Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoOnErrorReturn.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoOnErrorReturn.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoOnErrorReturn.java (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2022 VMware Inc. or its affiliates, All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reactor.core.publisher; + +import java.util.function.Predicate; + +import reactor.core.CoreSubscriber; +import reactor.util.annotation.Nullable; + +/** + * See {@link FluxOnErrorReturn}. + * + * @author Simon Baslé + */ +final class MonoOnErrorReturn extends InternalMonoOperator { + + @Nullable + final Predicate resumableErrorPredicate; + + @Nullable + final T fallbackValue; + + MonoOnErrorReturn(Mono source, @Nullable Predicate predicate, @Nullable T value) { + super(source); + resumableErrorPredicate = predicate; + fallbackValue = value; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new FluxOnErrorReturn.ReturnSubscriber<>(actual, resumableErrorPredicate, fallbackValue, false); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoPeekTerminal.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/MonoPeekTerminal.java (.../MonoPeekTerminal.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoPeekTerminal.java (.../MonoPeekTerminal.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -84,7 +84,7 @@ */ static final class MonoTerminalPeekSubscriber implements ConditionalSubscriber, InnerOperator, - Fuseable.QueueSubscription { + QueueSubscription { final CoreSubscriber actual; final ConditionalSubscriber actualConditional; @@ -94,7 +94,7 @@ //TODO could go into a common base for all-in-one subscribers? (as well as actual above) Subscription s; @Nullable - Fuseable.QueueSubscription queueSubscription; + QueueSubscription queueSubscription; int sourceMode; @@ -321,7 +321,22 @@ public T poll() { assert queueSubscription != null; boolean d = done; - T v = queueSubscription.poll(); + T v; + try { + v = queueSubscription.poll(); + } + catch (Throwable pe) { + if (parent.onErrorCall != null) { + try { + parent.onErrorCall.accept(pe); + } + catch (Throwable t) { + t = Operators.onOperatorError(null, pe, t, actual.currentContext()); + throw Exceptions.propagate(t); + } + } + throw pe; + } if (!valued && (v != null || d || sourceMode == SYNC)) { valued = true; //TODO include onEmptyCall here as well? @@ -334,16 +349,8 @@ actual.currentContext())); } } - if (parent.onAfterTerminateCall != null) { - try { - parent.onAfterTerminateCall.accept(v, null); - } - catch (Throwable t) { - Operators.onErrorDropped(Operators.onOperatorError(t, - actual.currentContext()), - actual.currentContext()); - } - } + //if parent.onAfterTerminateCall is set, fusion MUST be negotiated to NONE + //because there's no way to correctly support onAfterError in the poll() scenario } return v; } @@ -362,7 +369,13 @@ @Override public int requestFusion(int requestedMode) { int m; - if (queueSubscription == null) { //source wasn't actually Fuseable + if (queueSubscription == null || parent.onAfterTerminateCall != null) { + /* + Two cases where the configuration doesn't allow fusion: + - source wasn't actually Fuseable + - onAfterTerminateCall is set (which cannot be correctly implemented in the case + qs.poll() throws) + */ m = NONE; } else if ((requestedMode & THREAD_BARRIER) != 0) { @@ -380,5 +393,4 @@ return queueSubscription == null ? 0 : queueSubscription.size(); } } -} - +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoProcessor.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/MonoProcessor.java (.../MonoProcessor.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoProcessor.java (.../MonoProcessor.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoReduce.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/MonoReduce.java (.../MonoReduce.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoReduce.java (.../MonoReduce.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -53,36 +53,49 @@ return super.scanUnsafe(key); } - static final class ReduceSubscriber extends Operators.MonoSubscriber { + static final class ReduceSubscriber implements InnerOperator, + Fuseable, + QueueSubscription { + static final Object CANCELLED = new Object(); + final BiFunction aggregator; + final CoreSubscriber actual; + T aggregate; + Subscription s; boolean done; ReduceSubscriber(CoreSubscriber actual, BiFunction aggregator) { - super(actual); + this.actual = actual; this.aggregator = aggregator; } @Override + public CoreSubscriber actual() { + return this.actual; + } + + @Override @Nullable public Object scanUnsafe(Attr key) { if (key == Attr.TERMINATED) return done; + if (key == Attr.CANCELLED) return !done && aggregate == CANCELLED; + if (key == Attr.PREFETCH) return 0; if (key == Attr.PARENT) return s; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return super.scanUnsafe(key); + return InnerOperator.super.scanUnsafe(key); } @Override public void onSubscribe(Subscription s) { if (Operators.validate(this.s, s)) { this.s = s; actual.onSubscribe(this); - s.request(Long.MAX_VALUE); } } @@ -92,27 +105,45 @@ Operators.onNextDropped(t, actual.currentContext()); return; } - T r = this.value; + + final T r = this.aggregate; + if (r == CANCELLED) { + Operators.onDiscard(t, actual.currentContext()); + return; + } + + // initial scenario when aggregate has nothing in it if (r == null) { - setValue(t); + synchronized (this) { + if (this.aggregate == null) { + this.aggregate = t; + return; + } + } + + Operators.onDiscard(t, actual.currentContext()); } else { try { - r = Objects.requireNonNull(aggregator.apply(r, t), - "The aggregator returned a null value"); + synchronized (this) { + if (this.aggregate != CANCELLED) { + this.aggregate = Objects.requireNonNull(aggregator.apply(r, t), "The aggregator returned a null value"); + return; + } + } + Operators.onDiscard(t, actual.currentContext()); } catch (Throwable ex) { done = true; Context ctx = actual.currentContext(); + synchronized (this) { + this.aggregate = null; + } Operators.onDiscard(t, ctx); - Operators.onDiscard(this.value, ctx); - this.value = null; + Operators.onDiscard(r, ctx); actual.onError(Operators.onOperatorError(s, ex, t, actual.currentContext())); - return; } - - setValue(r); } } @@ -123,8 +154,22 @@ return; } done = true; - discard(this.value); - this.value = null; + + final T r; + synchronized (this) { + r = this.aggregate; + this.aggregate = null; + } + + if (r == CANCELLED) { + Operators.onErrorDropped(t, actual.currentContext()); + return; + } + + if (r != null) { + Operators.onDiscard(r, actual.currentContext()); + } + actual.onError(t); } @@ -134,19 +179,72 @@ return; } done = true; - T r = this.value; - if (r != null) { - complete(r); + + final T r; + synchronized (this) { + r = this.aggregate; + this.aggregate = null; } + + if (r == CANCELLED) { + return; + } + + if (r == null) { + actual.onComplete(); + } else { + actual.onNext(r); actual.onComplete(); } } @Override public void cancel() { - super.cancel(); s.cancel(); + + final T r; + synchronized (this) { + r = this.aggregate; + //noinspection unchecked + this.aggregate = (T) CANCELLED; + } + + if (r == null || r == CANCELLED) { + return; + } + + Operators.onDiscard(r, actual.currentContext()); } + + @Override + public void request(long n) { + s.request(Long.MAX_VALUE); + } + + @Override + public T poll() { + return null; + } + + @Override + public int requestFusion(int requestedMode) { + return Fuseable.NONE; + } + + @Override + public int size() { + return 0; + } + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public void clear() { + + } } } Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoReduceSeed.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/MonoReduceSeed.java (.../MonoReduceSeed.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoReduceSeed.java (.../MonoReduceSeed.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,6 @@ import java.util.function.BiFunction; import java.util.function.Supplier; -import org.reactivestreams.Subscription; import reactor.core.CoreSubscriber; import reactor.core.Fuseable; import reactor.util.annotation.Nullable; @@ -63,83 +62,71 @@ return super.scanUnsafe(key); } - static final class ReduceSeedSubscriber extends Operators.MonoSubscriber { + static final class ReduceSeedSubscriber extends Operators.BaseFluxToMonoOperator { final BiFunction accumulator; - Subscription s; + R seed; boolean done; ReduceSeedSubscriber(CoreSubscriber actual, BiFunction accumulator, - R value) { + R seed) { super(actual); this.accumulator = accumulator; - //noinspection deprecation - this.value = value; //setValue is made NO-OP in order to ignore redundant writes in base class + this.seed = seed; } @Override @Nullable public Object scanUnsafe(Attr key) { if (key == Attr.TERMINATED) return done; - if (key == Attr.PARENT) return s; - if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.CANCELLED) return !done && seed == null; return super.scanUnsafe(key); } @Override public void cancel() { - super.cancel(); s.cancel(); - } - @Override - public void setValue(R value) { - // value is updated directly in onNext. writes from the base class are redundant. - // if cancel() happens before first reduction, the seed is visible from constructor and will be discarded. - // if there was some accumulation in progress post cancel, onNext will take care of it. - } + final R seed; + synchronized (this) { + seed = this.seed; + this.seed = null; + } - @Override - public void onSubscribe(Subscription s) { - if (Operators.validate(this.s, s)) { - this.s = s; - - actual.onSubscribe(this); - - s.request(Long.MAX_VALUE); + if (seed == null) { + return; } + + Operators.onDiscard(seed, actual.currentContext()); } @Override public void onNext(T t) { - R v = this.value; - R accumulated; + final R v; + final R accumulated; - if (v != null) { //value null when cancelled - try { - accumulated = Objects.requireNonNull(accumulator.apply(v, t), - "The accumulator returned a null value"); - + try { + synchronized (this) { + v = this.seed; + if (v != null) { + accumulated = Objects.requireNonNull(accumulator.apply(v, t), + "The accumulator returned a null value"); + this.seed = accumulated; + return; + } } - catch (Throwable e) { - onError(Operators.onOperatorError(this, e, t, actual.currentContext())); - return; - } - if (STATE.get(this) == CANCELLED) { - discard(accumulated); - this.value = null; - } - else { - //noinspection deprecation - this.value = accumulated; //setValue is made NO-OP in order to ignore redundant writes in base class - } - } else { + + // the actual seed is null, meaning cancelled, new state have to be + // discarded as well Operators.onDiscard(t, actual.currentContext()); } + catch (Throwable e) { + onError(Operators.onOperatorError(this.s, e, t, actual.currentContext())); + } } @Override @@ -149,9 +136,20 @@ return; } done = true; - discard(this.value); - this.value = null; + final R seed; + synchronized (this) { + seed = this.seed; + this.seed = null; + } + + if (seed == null) { + Operators.onErrorDropped(t, actual.currentContext()); + return; + } + + Operators.onDiscard(seed, actual.currentContext()); + actual.onError(t); } @@ -162,8 +160,17 @@ } done = true; - complete(this.value); - //we DON'T null out the value, complete will do that once there's been a request + completePossiblyEmpty(); } + + @Override + R accumulatedValue() { + final R seed; + synchronized (this) { + seed = this.seed; + this.seed = null; + } + return seed; + } } } Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoSingleOptional.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoSingleOptional.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoSingleOptional.java (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reactor.core.publisher; + +import java.util.Optional; + +import org.reactivestreams.Subscription; + +import reactor.core.CoreSubscriber; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * Emits a single item from the source wrapped into an Optional, emits + * an empty Optional instead for empty source. + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class MonoSingleOptional extends InternalMonoOperator> { + + MonoSingleOptional(Mono source) { + super(source); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber> actual) { + return new SingleOptionalSubscriber<>(actual); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class SingleOptionalSubscriber extends Operators.MonoInnerProducerBase> implements InnerConsumer { + + Subscription s; + + boolean done; + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.TERMINATED) return done; + if (key == Attr.PARENT) return s; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return super.scanUnsafe(key); + } + + @Override + public Context currentContext() { + return actual().currentContext(); + } + + SingleOptionalSubscriber(CoreSubscriber> actual) { + super(actual); + } + + @Override + public void doOnRequest(long n) { + s.request(Long.MAX_VALUE); + } + + @Override + public void doOnCancel() { + s.cancel(); + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + actual().onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (done) { + Operators.onNextDropped(t, actual().currentContext()); + return; + } + done = true; + complete(Optional.of(t)); + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, actual().currentContext()); + return; + } + done = true; + actual().onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + complete(Optional.empty()); + } + + } +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoSingleOptionalCallable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoSingleOptionalCallable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoSingleOptionalCallable.java (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reactor.core.publisher; + +import reactor.core.CoreSubscriber; +import reactor.core.Exceptions; +import reactor.util.annotation.Nullable; + +import java.time.Duration; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.Callable; + +/** + * Emits a single item from the source wrapped into an Optional, emits + * an empty Optional instead for empty source. + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class MonoSingleOptionalCallable extends Mono> + implements Callable>, SourceProducer> { + + final Callable callable; + + MonoSingleOptionalCallable(Callable source) { + this.callable = Objects.requireNonNull(source, "source"); + } + + @Override + public void subscribe(CoreSubscriber> actual) { + Operators.MonoInnerProducerBase> + sds = new Operators.MonoInnerProducerBase<>(actual); + + actual.onSubscribe(sds); + + if (sds.isCancelled()) { + return; + } + + try { + T t = callable.call(); + sds.complete(Optional.ofNullable(t)); + } + catch (Throwable e) { + actual.onError(Operators.onOperatorError(e, actual.currentContext())); + } + + } + + @Override + public Optional block() { + //duration is ignored below + return block(Duration.ZERO); + } + + @Override + public Optional block(Duration m) { + final T v; + + try { + v = callable.call(); + } + catch (Throwable e) { + throw Exceptions.propagate(e); + } + + return Optional.ofNullable(v); + } + + @Override + public Optional call() throws Exception { + final T v = callable.call(); + + return Optional.ofNullable(v); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return null; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoSink.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/MonoSink.java (.../MonoSink.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoSink.java (.../MonoSink.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2015-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,7 @@ import reactor.core.Disposable; import reactor.util.annotation.Nullable; import reactor.util.context.Context; +import reactor.util.context.ContextView; /** * Wrapper API around an actual downstream Subscriber @@ -69,11 +70,24 @@ * operator or directly by a child subscriber overriding * {@link CoreSubscriber#currentContext()} * - * @return the current subscriber {@link Context}. + * @deprecated To be removed in 3.6.0 at the earliest. Prefer using #contextView() instead. */ + @Deprecated Context currentContext(); /** + * Return the current subscriber's context as a {@link ContextView} for inspection. + *

      + * {@link Context} can be enriched downstream via {@link Mono#contextWrite(Function)} + * operator or directly by a child subscriber overriding {@link CoreSubscriber#currentContext()}. + * + * @return the current subscriber {@link ContextView}. + */ + default ContextView contextView() { + return this.currentContext(); + } + + /** * Attaches a {@link LongConsumer} to this {@link MonoSink} that will be notified of * any request to this sink. * Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoSource.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/MonoSource.java (.../MonoSource.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoSource.java (.../MonoSource.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,16 +18,16 @@ import java.util.Objects; +import io.micrometer.context.ContextSnapshot; import org.reactivestreams.Publisher; +import org.reactivestreams.Subscription; import reactor.core.CorePublisher; import reactor.core.CoreSubscriber; import reactor.core.Scannable; import reactor.util.annotation.Nullable; +import reactor.util.context.Context; -import static reactor.core.Scannable.Attr.RUN_STYLE; -import static reactor.core.Scannable.Attr.RunStyle.SYNC; - /** * A decorating {@link Mono} {@link Publisher} that exposes {@link Mono} API over an arbitrary {@link Publisher} * Useful to create operators which return a {@link Mono}, e.g. : @@ -64,9 +64,12 @@ * @param actual */ @Override - @SuppressWarnings("unchecked") public void subscribe(CoreSubscriber actual) { - source.subscribe(actual); + if (ContextPropagationSupport.shouldPropagateContextToThreadLocals()) { + source.subscribe(new MonoSourceRestoringThreadLocalsSubscriber<>(actual)); + } else { + source.subscribe(actual); + } } @Override @@ -96,4 +99,89 @@ return null; } + static final class MonoSourceRestoringThreadLocalsSubscriber + implements InnerConsumer { + + final CoreSubscriber actual; + + Subscription s; + boolean done; + + MonoSourceRestoringThreadLocalsSubscriber(CoreSubscriber actual) { + this.actual = actual; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) { + return s; + } + if (key == Attr.RUN_STYLE) { + return Attr.RunStyle.SYNC; + } + if (key == Attr.ACTUAL) { + return actual; + } + return null; + } + + @Override + public Context currentContext() { + return actual.currentContext(); + } + + @SuppressWarnings("try") + @Override + public void onSubscribe(Subscription s) { + // This is needed, as the downstream can then switch threads, + // continue the subscription using different primitives and omit this operator + try (ContextSnapshot.Scope ignored = + ContextPropagation.setThreadLocals(actual.currentContext())) { + actual.onSubscribe(s); + } + } + + @SuppressWarnings("try") + @Override + public void onNext(T t) { + this.done = true; + try (ContextSnapshot.Scope ignored = + ContextPropagation.setThreadLocals(actual.currentContext())) { + actual.onNext(t); + actual.onComplete(); + } + } + + @SuppressWarnings("try") + @Override + public void onError(Throwable t) { + try (ContextSnapshot.Scope ignored = + ContextPropagation.setThreadLocals(actual.currentContext())) { + if (this.done) { + Operators.onErrorDropped(t, actual.currentContext()); + return; + } + + this.done = true; + + actual.onError(t); + } + } + + @SuppressWarnings("try") + @Override + public void onComplete() { + if (this.done) { + return; + } + + this.done = true; + + try (ContextSnapshot.Scope ignored = + ContextPropagation.setThreadLocals(actual.currentContext())) { + actual.onComplete(); + } + } + } } Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoStreamCollector.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/MonoStreamCollector.java (.../MonoStreamCollector.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoStreamCollector.java (.../MonoStreamCollector.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,14 +22,13 @@ import java.util.function.Function; import java.util.stream.Collector; -import org.reactivestreams.Subscription; import reactor.core.CoreSubscriber; import reactor.core.Fuseable; import reactor.util.annotation.Nullable; import reactor.util.context.Context; /** - * Collects the values from the source sequence into a {@link java.util.stream.Collector} + * Collects the values from the source sequence into a {@link Collector} * instance. * * @param the source value type @@ -67,17 +66,14 @@ return super.scanUnsafe(key); } - static final class StreamCollectorSubscriber - extends Operators.MonoSubscriber { + static final class StreamCollectorSubscriber extends Operators.BaseFluxToMonoOperator { final BiConsumer accumulator; final Function finisher; A container; //not final to be able to null it out on termination - Subscription s; - boolean done; StreamCollectorSubscriber(CoreSubscriber actual, @@ -94,13 +90,11 @@ @Nullable public Object scanUnsafe(Attr key) { if (key == Attr.TERMINATED) return done; - if (key == Attr.PARENT) return s; - if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; return super.scanUnsafe(key); } - protected void discardIntermediateContainer(A a) { + void discardIntermediateContainer(A a) { Context ctx = actual.currentContext(); if (a instanceof Collection) { Operators.onDiscardMultiple((Collection) a, ctx); @@ -109,27 +103,22 @@ Operators.onDiscard(a, ctx); } } - //NB: value and thus discard are not used @Override - public void onSubscribe(Subscription s) { - if (Operators.validate(this.s, s)) { - this.s = s; - - actual.onSubscribe(this); - - s.request(Long.MAX_VALUE); - } - } - - @Override public void onNext(T t) { if (done) { Operators.onNextDropped(t, actual.currentContext()); return; } try { - accumulator.accept(container, t); + synchronized (this) { + final A container = this.container; + if (container != null) { + accumulator.accept(container, t); + return; + } + } + Operators.onDiscard(t, actual.currentContext()); } catch (Throwable ex) { Context ctx = actual.currentContext(); @@ -145,8 +134,18 @@ return; } done = true; - discardIntermediateContainer(container); - container = null; + final A c; + synchronized (this) { + c = container; + container = null; + } + + if (c == null) { + Operators.onErrorDropped(t, actual.currentContext()); + return; + } + + discardIntermediateContainer(c); actual.onError(t); } @@ -157,33 +156,50 @@ } done = true; - A a = container; - container = null; + completePossiblyEmpty(); + } - R r; + @Override + public void cancel() { + super.cancel(); + final A c; + synchronized (this) { + c = container; + container = null; + } + if (c != null) { + discardIntermediateContainer(c); + } + } + + R accumulatedValue() { + final A c; + synchronized (this) { + c = container; + container = null; + } + + if (c == null) { + return null; + } + + R r; try { - r = finisher.apply(a); + r = finisher.apply(c); } catch (Throwable ex) { - discardIntermediateContainer(a); + discardIntermediateContainer(c); actual.onError(Operators.onOperatorError(ex, actual.currentContext())); - return; + return null; } if (r == null) { - actual.onError(Operators.onOperatorError(new NullPointerException("Collector returned null"), actual.currentContext())); - return; + actual.onError(Operators.onOperatorError(new NullPointerException( + "Collector returned null"), actual.currentContext())); } - complete(r); - } - @Override - public void cancel() { - super.cancel(); - s.cancel(); - discardIntermediateContainer(container); - container = null; + return r; } } -} +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoSubscribeOn.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/MonoSubscribeOn.java (.../MonoSubscribeOn.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoSubscribeOn.java (.../MonoSubscribeOn.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -45,7 +45,7 @@ @Override public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { - Scheduler.Worker worker = scheduler.createWorker(); + Worker worker = scheduler.createWorker(); SubscribeOnSubscriber parent = new SubscribeOnSubscriber<>(source, actual, worker); @@ -78,7 +78,7 @@ final Publisher parent; - final Scheduler.Worker worker; + final Worker worker; volatile Subscription s; @SuppressWarnings("rawtypes") @@ -211,4 +211,4 @@ worker.dispose(); } } -} +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoSubscribeOnCallable.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/MonoSubscribeOnCallable.java (.../MonoSubscribeOnCallable.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoSubscribeOnCallable.java (.../MonoSubscribeOnCallable.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -59,10 +59,10 @@ } @Override - public Object scanUnsafe(Scannable.Attr key) { - if (key == Scannable.Attr.RUN_ON) return scheduler; + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_ON) return scheduler; if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; return null; } -} +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoSupplier.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/MonoSupplier.java (.../MonoSupplier.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoSupplier.java (.../MonoSupplier.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import java.time.Duration; import java.util.Objects; import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.function.Supplier; @@ -44,27 +45,7 @@ @Override public void subscribe(CoreSubscriber actual) { - Operators.MonoSubscriber - sds = new Operators.MonoSubscriber<>(actual); - - actual.onSubscribe(sds); - - if (sds.isCancelled()) { - return; - } - - try { - T t = supplier.get(); - if (t == null) { - sds.onComplete(); - } - else { - sds.complete(t); - } - } - catch (Throwable e) { - actual.onError(Operators.onOperatorError(e, actual.currentContext())); - } + actual.onSubscribe(new MonoSupplierSubscription<>(actual, supplier)); } @Override @@ -91,4 +72,105 @@ if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; return null; } + + static class MonoSupplierSubscription + implements InnerProducer, Fuseable, QueueSubscription { + + final CoreSubscriber actual; + final Supplier supplier; + + boolean done; + + volatile int requestedOnce; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater REQUESTED_ONCE = + AtomicIntegerFieldUpdater.newUpdater(MonoSupplierSubscription.class, "requestedOnce"); + + volatile boolean cancelled; + + MonoSupplierSubscription(CoreSubscriber actual, Supplier callable) { + this.actual = actual; + this.supplier = callable; + } + + @Override + public CoreSubscriber actual() { + return this.actual; + } + + @Override + public T poll() { + if (this.done) { + return null; + } + + this.done = true; + + return this.supplier.get(); + } + + @Override + public void request(long n) { + if (this.cancelled) { + return; + } + + if (this.requestedOnce == 1 || !REQUESTED_ONCE.compareAndSet(this, 0 , 1)) { + return; + } + + final CoreSubscriber s = this.actual; + + final T value; + try { + value = this.supplier.get(); + } + catch (Exception e) { + if (this.cancelled) { + Operators.onErrorDropped(e, s.currentContext()); + return; + } + + s.onError(e); + return; + } + + + if (this.cancelled) { + Operators.onDiscard(value, s.currentContext()); + return; + } + + if (value != null) { + s.onNext(value); + } + + s.onComplete(); + } + + @Override + public void cancel() { + this.cancelled = true; + } + + @Override + public int requestFusion(int requestedMode) { + return requestedMode & SYNC; + } + + @Override + public int size() { + return this.done ? 0 : 1; + } + + @Override + public boolean isEmpty() { + return this.done; + } + + @Override + public void clear() { + this.done = true; + } + } } Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoTakeLastOne.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/MonoTakeLastOne.java (.../MonoTakeLastOne.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoTakeLastOne.java (.../MonoTakeLastOne.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,6 @@ import java.util.NoSuchElementException; import java.util.Objects; -import org.reactivestreams.Subscription; import reactor.core.CoreSubscriber; import reactor.core.Fuseable; import reactor.util.annotation.Nullable; @@ -56,74 +55,142 @@ } static final class TakeLastOneSubscriber - extends Operators.MonoSubscriber { + extends Operators.BaseFluxToMonoOperator { + static final Object CANCELLED = new Object(); + final boolean mustEmit; - final T defaultValue; - Subscription s; + T value; + + boolean done; + TakeLastOneSubscriber(CoreSubscriber actual, @Nullable T defaultValue, boolean mustEmit) { super(actual); - this.defaultValue = defaultValue; + this.value = defaultValue; this.mustEmit = mustEmit; } @Override - public void onSubscribe(Subscription s) { - if (Operators.validate(this.s, s)) { - this.s = s; - - actual.onSubscribe(this); - - s.request(Long.MAX_VALUE); - } - - } - - @Override @Nullable public Object scanUnsafe(Attr key) { - if (key == Attr.PARENT) return s; - if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.TERMINATED) return done && value == null; + if (key == Attr.CANCELLED) return value == CANCELLED; return super.scanUnsafe(key); } @Override public void onNext(T t) { T old = this.value; - setValue(t); + if (old == CANCELLED) { + // cancelled + Operators.onDiscard(t, actual.currentContext()); + return; + } + + synchronized (this) { + old = this.value; + if (old != CANCELLED) { + this.value = t; + } + } + + if (old == CANCELLED) { + // cancelled + Operators.onDiscard(t, actual.currentContext()); + return; + } + Operators.onDiscard(old, actual.currentContext()); //FIXME cache context } @Override + public void onError(Throwable t) { + if (this.done) { + Operators.onErrorDropped(t, actual.currentContext()); + return; + } + this.done = true; + + + final T v; + synchronized (this) { + v = this.value; + this.value = null; + } + + if (v == CANCELLED) { + Operators.onErrorDropped(t, actual.currentContext()); + return; + } + + if (v != null) { + Operators.onDiscard(v, actual.currentContext()); + } + + this.actual.onError(t); + } + + @Override public void onComplete() { - T v = this.value; + if (this.done) { + return; + } + this.done = true; + + final T v = this.value; + + if (v == CANCELLED) { + return; + } + if (v == null) { if (mustEmit) { - if(defaultValue != null){ - complete(defaultValue); - } - else { - actual.onError(Operators.onOperatorError(new NoSuchElementException( - "Flux#last() didn't observe any " + "onNext signal"), - actual.currentContext())); - } + actual.onError(Operators.onOperatorError(new NoSuchElementException( + "Flux#last() didn't observe any " + "onNext signal"), + actual.currentContext())); } else { actual.onComplete(); } return; } - complete(v); + + completePossiblyEmpty(); } @Override public void cancel() { - super.cancel(); s.cancel(); + + final T v; + synchronized (this) { + v = this.value; + //noinspection unchecked + this.value = (T) CANCELLED; + } + + if (v != null) { + Operators.onDiscard(v, actual.currentContext()); + } } + + @Override + T accumulatedValue() { + final T v; + synchronized (this) { + v = this.value; + this.value = null; + } + + if (v == CANCELLED) { + return null; + } + + return v; + } } } Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoTap.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoTap.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoTap.java (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2022-2023 VMware Inc. or its affiliates, All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reactor.core.publisher; + +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.core.observability.SignalListener; +import reactor.core.observability.SignalListenerFactory; +import reactor.core.publisher.FluxTap.TapSubscriber; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * A generic per-Subscription side effect {@link Mono} that notifies a {@link SignalListener} of most events. + * + * @author Simon Baslé + */ +final class MonoTap extends InternalMonoOperator { + + final SignalListenerFactory tapFactory; + final STATE commonTapState; + + MonoTap(Mono source, SignalListenerFactory tapFactory) { + super(source); + this.tapFactory = tapFactory; + this.commonTapState = tapFactory.initializePublisherState(source); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) throws Throwable { + //if the SignalListener cannot be created, all we can do is error the subscriber. + //after it is created, in case doFirst fails we can additionally try to invoke doFinally. + //note that if the later handler also fails, then that exception is thrown. + SignalListener signalListener; + try { + //TODO replace currentContext() with contextView() when available + signalListener = tapFactory.createListener(source, actual.currentContext().readOnly(), commonTapState); + } + catch (Throwable generatorError) { + Operators.error(actual, generatorError); + return null; + } + // Attempt to wrap the SignalListener with one that restores ThreadLocals from Context on each listener methods + // (only if ContextPropagation.isContextPropagationAvailable() is true) + signalListener = ContextPropagationSupport.isContextPropagationAvailable() ? + ContextPropagation.contextRestoreForTap(signalListener, actual::currentContext) : signalListener; + + try { + signalListener.doFirst(); + } + catch (Throwable listenerError) { + signalListener.handleListenerError(listenerError); + Operators.error(actual, listenerError); + return null; + } + + // Invoked AFTER doFirst + Context ctx; + try { + ctx = signalListener.addToContext(actual.currentContext()); + } + catch (Throwable e) { + IllegalStateException listenerError = new IllegalStateException( + "Unable to augment tap Context at subscription via addToContext", e); + signalListener.handleListenerError(listenerError); + Operators.error(actual, listenerError); + return null; + } + + if (actual instanceof Fuseable.ConditionalSubscriber) { + //noinspection unchecked + return new FluxTap.TapConditionalSubscriber<>((Fuseable.ConditionalSubscriber) actual, signalListener, ctx); + } + return new TapSubscriber<>(actual, signalListener, ctx); + } + + @Nullable + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.PREFETCH) return -1; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return super.scanUnsafe(key); + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoTapFuseable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoTapFuseable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoTapFuseable.java (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2022-2023 VMware Inc. or its affiliates, All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reactor.core.publisher; + +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.core.observability.SignalListener; +import reactor.core.observability.SignalListenerFactory; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * A {@link Fuseable} generic per-Subscription side effect {@link Mono} that notifies a {@link SignalListener} of most events. + * + * @author Simon Baslé + */ +final class MonoTapFuseable extends InternalMonoOperator implements Fuseable { + + final SignalListenerFactory tapFactory; + final STATE commonTapState; + + MonoTapFuseable(Mono source, SignalListenerFactory tapFactory) { + super(source); + this.tapFactory = tapFactory; + this.commonTapState = tapFactory.initializePublisherState(source); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) throws Throwable { + //if the SignalListener cannot be created, all we can do is error the subscriber. + //after it is created, in case doFirst fails we can additionally try to invoke doFinally. + //note that if the later handler also fails, then that exception is thrown. + SignalListener signalListener; + try { + //TODO replace currentContext() with contextView() when available + signalListener = tapFactory.createListener(source, actual.currentContext().readOnly(), commonTapState); + } + catch (Throwable generatorError) { + Operators.error(actual, generatorError); + return null; + } + // Attempt to wrap the SignalListener with one that restores ThreadLocals from Context on each listener methods + // (only if ContextPropagation.isContextPropagationAvailable() is true) + signalListener = ContextPropagationSupport.isContextPropagationAvailable() ? + ContextPropagation.contextRestoreForTap(signalListener, actual::currentContext) : signalListener; + + try { + signalListener.doFirst(); + } + catch (Throwable listenerError) { + signalListener.handleListenerError(listenerError); + Operators.error(actual, listenerError); + return null; + } + + // Invoked AFTER doFirst + Context ctx; + try { + ctx = signalListener.addToContext(actual.currentContext()); + } + catch (Throwable e) { + IllegalStateException listenerError = new IllegalStateException( + "Unable to augment tap Context at subscription via addToContext", e); + signalListener.handleListenerError(listenerError); + Operators.error(actual, listenerError); + return null; + } + + if (actual instanceof ConditionalSubscriber) { + //noinspection unchecked + return new FluxTapFuseable.TapConditionalFuseableSubscriber<>( + (ConditionalSubscriber) actual, signalListener, ctx); + } + return new FluxTapFuseable.TapFuseableSubscriber<>(actual, signalListener, ctx); + } + + @Nullable + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.PREFETCH) return -1; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return super.scanUnsafe(key); + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoTapRestoringThreadLocals.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoTapRestoringThreadLocals.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoTapRestoringThreadLocals.java (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2022-2023 VMware Inc. or its affiliates, All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reactor.core.publisher; + +import io.micrometer.context.ContextSnapshot; +import reactor.core.CoreSubscriber; +import reactor.core.observability.SignalListener; +import reactor.core.observability.SignalListenerFactory; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * A generic per-Subscription side effect {@link Mono} that notifies a {@link SignalListener} of most events. + * + * @author Simon Baslé + */ +final class MonoTapRestoringThreadLocals extends MonoOperator { + + final SignalListenerFactory tapFactory; + final STATE commonTapState; + + MonoTapRestoringThreadLocals(Mono source, SignalListenerFactory tapFactory) { + super(source); + this.tapFactory = tapFactory; + this.commonTapState = tapFactory.initializePublisherState(source); + } + + @Override + public void subscribe(CoreSubscriber actual) { + //if the SignalListener cannot be created, all we can do is error the subscriber. + //after it is created, in case doFirst fails we can additionally try to invoke doFinally. + //note that if the later handler also fails, then that exception is thrown. + SignalListener signalListener; + try { + //TODO replace currentContext() with contextView() when available + signalListener = tapFactory.createListener(source, actual.currentContext().readOnly(), commonTapState); + } + catch (Throwable generatorError) { + Operators.error(actual, generatorError); + return; + } + + try { + signalListener.doFirst(); + } + catch (Throwable listenerError) { + signalListener.handleListenerError(listenerError); + Operators.error(actual, listenerError); + return; + } + + // invoked AFTER doFirst + Context alteredContext; + try { + alteredContext = signalListener.addToContext(actual.currentContext()); + } + catch (Throwable e) { + signalListener.handleListenerError(new IllegalStateException("Unable to augment tap Context at construction via addToContext", e)); + alteredContext = actual.currentContext(); + } + + try (ContextSnapshot.Scope ignored = ContextPropagation.setThreadLocals(alteredContext)) { + source.subscribe(new FluxTapRestoringThreadLocals.TapSubscriber<>(actual, signalListener, alteredContext)); + } + } + + @Nullable + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.PREFETCH) return -1; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return super.scanUnsafe(key); + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoTimeout.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/MonoTimeout.java (.../MonoTimeout.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoTimeout.java (.../MonoTimeout.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -66,7 +66,7 @@ @SuppressWarnings("unchecked") public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { return new FluxTimeout.TimeoutMainSubscriber( - Operators.serialize(actual), + actual, firstTimeout, NEVER, other, Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoUsingWhen.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/MonoUsingWhen.java (.../MonoUsingWhen.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoUsingWhen.java (.../MonoUsingWhen.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2018-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,9 +24,9 @@ import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; + import reactor.core.CoreSubscriber; import reactor.core.Exceptions; -import reactor.core.Fuseable.ConditionalSubscriber; import reactor.core.publisher.FluxUsingWhen.UsingWhenSubscriber; import reactor.core.publisher.Operators.DeferredSubscription; import reactor.util.annotation.Nullable; @@ -151,8 +151,6 @@ Subscription resourceSubscription; boolean resourceProvided; - UsingWhenSubscriber closureSubscriber; - ResourceSubscriber(CoreSubscriber actual, Function> resourceClosure, Function> asyncComplete, @@ -182,16 +180,13 @@ final Mono p = deriveMonoFromResource(resource, resourceClosure); - this.closureSubscriber = - prepareSubscriberForResource(resource, - this.actual, - this.asyncComplete, - this.asyncError, - this.asyncCancel, - this); + p.subscribe(MonoUsingWhen.prepareSubscriberForResource(resource, + this.actual, + this.asyncComplete, + this.asyncError, + this.asyncCancel, + this)); - p.subscribe(closureSubscriber); - if (!isMonoSource) { resourceSubscription.cancel(); } @@ -231,14 +226,9 @@ public void cancel() { if (!resourceProvided) { resourceSubscription.cancel(); - super.cancel(); } - else { - super.terminate(); - if (closureSubscriber != null) { - closureSubscriber.cancel(); - } - } + + super.cancel(); } @Override @@ -253,7 +243,7 @@ } } - static class MonoUsingWhenSubscriber extends FluxUsingWhen.UsingWhenSubscriber { + static class MonoUsingWhenSubscriber extends UsingWhenSubscriber { MonoUsingWhenSubscriber(CoreSubscriber actual, S resource, @@ -287,4 +277,4 @@ this.actual.onError(error); } } -} +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoWhen.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/MonoWhen.java (.../MonoWhen.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoWhen.java (.../MonoWhen.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2017-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,14 +17,15 @@ package reactor.core.publisher; import java.util.Objects; -import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import java.util.stream.Stream; import org.reactivestreams.Publisher; import org.reactivestreams.Subscription; import reactor.core.CoreSubscriber; import reactor.core.Exceptions; +import reactor.core.Fuseable; import reactor.core.Scannable; import reactor.util.annotation.Nullable; import reactor.util.context.Context; @@ -94,9 +95,8 @@ return; } - WhenCoordinator parent = new WhenCoordinator(actual, n, delayError); + WhenCoordinator parent = new WhenCoordinator(a, actual, n, delayError); actual.onSubscribe(parent); - parent.subscribe(a); } @Override @@ -107,22 +107,34 @@ return null; } - static final class WhenCoordinator extends Operators.MonoSubscriber { + static final class WhenCoordinator implements InnerProducer, + Fuseable, + Fuseable.QueueSubscription { + final CoreSubscriber actual; + final Publisher[] sources; final WhenInner[] subscribers; final boolean delayError; - volatile int done; - @SuppressWarnings("rawtypes") - static final AtomicIntegerFieldUpdater DONE = - AtomicIntegerFieldUpdater.newUpdater(WhenCoordinator.class, "done"); + volatile long state; + static final AtomicLongFieldUpdater STATE = + AtomicLongFieldUpdater.newUpdater(WhenCoordinator.class, "state"); - @SuppressWarnings("unchecked") - WhenCoordinator(CoreSubscriber subscriber, + static final long INTERRUPTED_FLAG = + 0b1000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000L; + static final long REQUESTED_ONCE_FLAG = + 0b0100_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000L; + static final long MAX_SIGNALS_VALUE = + 0b0000_0000_0000_0000_0000_0000_0000_0000_0111_1111_1111_1111_1111_1111_1111_1111L; + + WhenCoordinator( + Publisher[] sources, + CoreSubscriber actual, int n, boolean delayError) { - super(subscriber); + this.sources = sources; + this.actual = actual; this.delayError = delayError; subscribers = new WhenInner[n]; for (int i = 0; i < n; i++) { @@ -131,55 +143,42 @@ } @Override + public CoreSubscriber actual() { + return this.actual; + } + + @Override @Nullable public Object scanUnsafe(Attr key) { - if (key == Attr.TERMINATED) { - return done == subscribers.length; + if (key == Attr.TERMINATED) return deliveredSignals(this.state) == subscribers.length; + if (key == Attr.BUFFERED) return subscribers.length; + if (key == Attr.DELAY_ERROR) return delayError; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.CANCELLED) { + final long state = this.state; + return isInterrupted(state) && deliveredSignals(state) != subscribers.length; } - if (key == Attr.BUFFERED) { - return subscribers.length; - } - if (key == Attr.DELAY_ERROR) { - return delayError; - } - if (key == Attr.RUN_STYLE) { - return Attr.RunStyle.SYNC; - } - return super.scanUnsafe(key); + return InnerProducer.super.scanUnsafe(key); } @Override public Stream inners() { return Stream.of(subscribers); } - void subscribe(Publisher[] sources) { - WhenInner[] a = subscribers; - for (int i = 0; i < a.length; i++) { - sources[i].subscribe(a[i]); - } - } + boolean signal() { + final WhenInner[] a = subscribers; + int n = a.length; - void signalError(Throwable t) { - if (delayError) { - signal(); + final long previousState = markDeliveredSignal(this); + final int deliveredSignals = deliveredSignals(previousState); + if (isInterrupted(previousState) || deliveredSignals == n) { + return false; } - else { - int n = subscribers.length; - if (DONE.getAndSet(this, n) != n) { - cancel(); - actual.onError(t); - } - } - } - @SuppressWarnings("unchecked") - void signal() { - WhenInner[] a = subscribers; - int n = a.length; - if (DONE.incrementAndGet(this) != n) { - return; + if ((deliveredSignals + 1) != n) { + return true; } Throwable error = null; @@ -200,7 +199,6 @@ error = e; } } - } if (compositeError != null) { @@ -212,25 +210,150 @@ else { actual.onComplete(); } + + return true; } @Override + public void request(long n) { + final long previousState = markRequestedOnce(this); + if (isRequestedOnce(previousState) || isInterrupted(previousState)) { + return; + } + + final Publisher[] sources = this.sources; + final WhenInner[] subs = this.subscribers; + for (int i = 0; i < subscribers.length; i++) { + sources[i].subscribe(subs[i]); + } + } + + @Override public void cancel() { - if (!isCancelled()) { - super.cancel(); - for (WhenInner ms : subscribers) { + final long previousState = markInterrupted(this); + if (isInterrupted(previousState) || !isRequestedOnce(previousState) || deliveredSignals(previousState) == subscribers.length) { + return; + } + + for (WhenInner ms : subscribers) { + ms.cancel(); + } + } + + void cancelExcept(WhenInner source) { + for (WhenInner ms : subscribers) { + if (ms != source) { ms.cancel(); } } } + + @Override + public int requestFusion(int requestedMode) { + return Fuseable.NONE; + } + + @Override + public Void poll() { + return null; + } + + @Override + public int size() { + return 0; + } + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public void clear() { + + } + + static long markRequestedOnce(WhenCoordinator instance) { + for (;;) { + final long state = instance.state; + + if (isInterrupted(state) || isRequestedOnce(state)) { + return state; + } + + final long nextState = state | REQUESTED_ONCE_FLAG; + if (STATE.compareAndSet(instance, state, nextState)) { + return state; + } + } + } + + static long markDeliveredSignal(WhenCoordinator instance) { + final int n = instance.subscribers.length; + for (;;) { + final long state = instance.state; + + if (isInterrupted(state) || n == deliveredSignals(state)) { + return state; + } + + final long nextState = state + 1; + if (STATE.compareAndSet(instance, state, nextState)) { + return state; + } + } + } + + static long markForceTerminated(WhenCoordinator instance) { + final int n = instance.subscribers.length; + for (;;) { + final long state = instance.state; + + if (isInterrupted(state) || n == deliveredSignals(state)) { + return state; + } + + final long nextState = (state &~ MAX_SIGNALS_VALUE) | n | INTERRUPTED_FLAG; + if (STATE.compareAndSet(instance, state, nextState)) { + return state; + } + } + } + + static long markInterrupted(WhenCoordinator instance) { + final int n = instance.subscribers.length; + for (;;) { + final long state = instance.state; + + if (isInterrupted(state) || n == deliveredSignals(state)) { + return state; + } + + final long nextState = state | INTERRUPTED_FLAG; + if (STATE.compareAndSet(instance, state, nextState)) { + return state; + } + } + } + + static boolean isRequestedOnce(long state) { + return (state & REQUESTED_ONCE_FLAG) == REQUESTED_ONCE_FLAG; + } + + static int deliveredSignals(long state) { + return (int) (state & Integer.MAX_VALUE); + } + + static boolean isInterrupted(long state) { + return (state & INTERRUPTED_FLAG) == INTERRUPTED_FLAG; + } } static final class WhenInner implements InnerConsumer { final WhenCoordinator parent; volatile Subscription s; - @SuppressWarnings("rawtypes") static final AtomicReferenceFieldUpdater S = AtomicReferenceFieldUpdater.newUpdater(WhenInner.class, Subscription.class, @@ -265,27 +388,38 @@ @Override public Context currentContext() { - return parent.currentContext(); + return parent.actual.currentContext(); } @Override public void onSubscribe(Subscription s) { if (Operators.setOnce(S, this, s)) { s.request(Long.MAX_VALUE); } - else { - s.cancel(); - } } @Override public void onNext(Object t) { + Operators.onDiscard(t, currentContext()); } @Override public void onError(Throwable t) { error = t; - parent.signalError(t); + if (parent.delayError) { + if (!parent.signal()) { + Operators.onErrorDropped(t, parent.actual.currentContext()); + } + } + else { + final long previousState = WhenCoordinator.markForceTerminated(parent); + if (WhenCoordinator.isInterrupted(previousState)) { + return; + } + + parent.cancelExcept(this); + parent.actual.onError(t); + } } @Override Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoZip.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/MonoZip.java (.../MonoZip.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoZip.java (.../MonoZip.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,17 +16,18 @@ package reactor.core.publisher; +import java.util.Arrays; import java.util.Objects; -import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import java.util.function.BiFunction; import java.util.function.Function; import java.util.stream.Stream; -import org.reactivestreams.Publisher; import org.reactivestreams.Subscription; import reactor.core.CoreSubscriber; import reactor.core.Exceptions; +import reactor.core.Fuseable; import reactor.core.Scannable; import reactor.util.annotation.Nullable; import reactor.util.context.Context; @@ -41,16 +42,16 @@ final boolean delayError; - final Publisher[] sources; + final Mono[] sources; - final Iterable> sourcesIterable; + final Iterable> sourcesIterable; final Function zipper; @SuppressWarnings("unchecked") MonoZip(boolean delayError, - Publisher p1, - Publisher p2, + Mono p1, + Mono p2, BiFunction zipper2) { this(delayError, new FluxZip.PairwiseZipper<>(new BiFunction[]{ @@ -61,7 +62,7 @@ MonoZip(boolean delayError, Function zipper, - Publisher... sources) { + Mono... sources) { this.delayError = delayError; this.zipper = Objects.requireNonNull(zipper, "zipper"); this.sources = Objects.requireNonNull(sources, "sources"); @@ -70,7 +71,7 @@ MonoZip(boolean delayError, Function zipper, - Iterable> sourcesIterable) { + Iterable> sourcesIterable) { this.delayError = delayError; this.zipper = Objects.requireNonNull(zipper, "zipper"); this.sources = null; @@ -79,11 +80,11 @@ @SuppressWarnings("unchecked") @Nullable - Mono zipAdditionalSource(Publisher source, BiFunction zipper) { - Publisher[] oldSources = sources; + Mono zipAdditionalSource(Mono source, BiFunction zipper) { + Mono[] oldSources = sources; if (oldSources != null && this.zipper instanceof FluxZip.PairwiseZipper) { int oldLen = oldSources.length; - Publisher[] newSources = new Publisher[oldLen + 1]; + Mono[] newSources = new Mono[oldLen + 1]; System.arraycopy(oldSources, 0, newSources, 0, oldLen); newSources[oldLen] = source; @@ -98,17 +99,17 @@ @SuppressWarnings("unchecked") @Override public void subscribe(CoreSubscriber actual) { - Publisher[] a; + Mono[] a; int n = 0; if (sources != null) { a = sources; n = a.length; } else { - a = new Publisher[8]; - for (Publisher m : sourcesIterable) { + a = new Mono[8]; + for (Mono m : sourcesIterable) { if (n == a.length) { - Publisher[] b = new Publisher[n + (n >> 2)]; + Mono[] b = new Mono[n + (n >> 2)]; System.arraycopy(a, 0, b, 0, n); a = b; } @@ -121,12 +122,7 @@ return; } - ZipCoordinator parent = new ZipCoordinator<>(actual, n, delayError, zipper); - actual.onSubscribe(parent); - ZipInner[] subs = parent.subscribers; - for (int i = 0; i < n; i++) { - a[i].subscribe(subs[i]); - } + actual.onSubscribe(new ZipCoordinator<>(a, actual, n, delayError, zipper)); } @Override @@ -136,65 +132,110 @@ return null; } - static final class ZipCoordinator extends Operators.MonoSubscriber { + static final class ZipCoordinator implements InnerProducer, + Fuseable, + Fuseable.QueueSubscription { + + final Mono[] sources; + final ZipInner[] subscribers; + final CoreSubscriber actual; + final boolean delayError; final Function zipper; - volatile int done; + volatile long state; + @SuppressWarnings("rawtypes") - static final AtomicIntegerFieldUpdater DONE = - AtomicIntegerFieldUpdater.newUpdater(ZipCoordinator.class, "done"); + static final AtomicLongFieldUpdater STATE = + AtomicLongFieldUpdater.newUpdater(ZipCoordinator.class, "state"); + static final long INTERRUPTED_FLAG = + 0b1000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000L; + static final long REQUESTED_ONCE_FLAG = + 0b0100_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000L; + static final long MAX_SIGNALS_VALUE = + 0b0000_0000_0000_0000_0000_0000_0000_0000_0111_1111_1111_1111_1111_1111_1111_1111L; + @SuppressWarnings("unchecked") - ZipCoordinator(CoreSubscriber subscriber, + ZipCoordinator( + Mono[] sources, + CoreSubscriber subscriber, int n, boolean delayError, Function zipper) { - super(subscriber); + this.sources = sources; + this.actual = subscriber; this.delayError = delayError; this.zipper = zipper; - subscribers = new ZipInner[n]; + final ZipInner[] ss = new ZipInner[n]; + this.subscribers = ss; for (int i = 0; i < n; i++) { - subscribers[i] = new ZipInner<>(this); + ss[i] = new ZipInner<>(this); } } @Override + public CoreSubscriber actual() { + return this.actual; + } + + @Override @Nullable public Object scanUnsafe(Attr key) { - if (key == Attr.TERMINATED) { - return done == subscribers.length; + if (key == Attr.TERMINATED) return deliveredSignals(this.state) == subscribers.length; + if (key == Attr.BUFFERED) return subscribers.length; + if (key == Attr.DELAY_ERROR) return delayError; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.CANCELLED) { + final long state = this.state; + return isInterrupted(state) && deliveredSignals(state) != subscribers.length; } - if (key == Attr.BUFFERED) { - return subscribers.length; + + return InnerProducer.super.scanUnsafe(key); + } + + @Override + public int requestFusion(int requestedMode) { + return Fuseable.NONE; + } + + @Override + public void request(long n) { + final long previousState = markRequestedOnce(this); + if (isRequestedOnce(previousState) || isInterrupted(previousState)) { + return; } - if (key == Attr.DELAY_ERROR) { - return delayError; - } - if (key == Attr.RUN_STYLE) { - return Attr.RunStyle.SYNC; - } - return super.scanUnsafe(key); + final Mono[] monos = this.sources; + final ZipInner[] subs = this.subscribers; + for (int i = 0; i < subscribers.length; i++) { + monos[i].subscribe(subs[i]); + } } @Override public Stream inners() { return Stream.of(subscribers); } - @SuppressWarnings("unchecked") - void signal() { + boolean signal() { ZipInner[] a = subscribers; int n = a.length; - if (DONE.incrementAndGet(this) != n) { - return; + + final long previousState = markDeliveredSignal(this); + final int deliveredSignals = deliveredSignals(previousState); + if (isInterrupted(previousState) || deliveredSignals == n) { + return false; } + if ((deliveredSignals + 1) != n) { + return true; + } + Object[] o = new Object[n]; Throwable error = null; Throwable compositeError = null; @@ -242,36 +283,138 @@ "zipper produced a null value"); } catch (Throwable t) { + Operators.onDiscardMultiple(Arrays.asList(o), actual.currentContext()); actual.onError(Operators.onOperatorError(null, t, o, actual.currentContext())); - return; + return true; } - complete(r); + actual.onNext(r); + actual.onComplete(); } + + return true; } @Override public void cancel() { - if (!isCancelled()) { - super.cancel(); - for (ZipInner ms : subscribers) { - ms.cancel(); + final long previousState = markInterrupted(this); + if (isInterrupted(previousState) || !isRequestedOnce(previousState) || deliveredSignals(previousState) == subscribers.length) { + return; + } + + final Context context = actual.currentContext(); + for (ZipInner ms : subscribers) { + if (ms.cancel()) { + Operators.onDiscard(ms.value, context); } } } void cancelExcept(ZipInner source) { - if (!isCancelled()) { - super.cancel(); - for (ZipInner ms : subscribers) { - if(ms != source) { - ms.cancel(); - } + final Context context = actual.currentContext(); + for (ZipInner ms : subscribers) { + if (ms != source && ms.cancel()) { + Operators.onDiscard(ms.value, context); } } } + + @Override + public R poll() { + return null; + } + + @Override + public int size() { + return 0; + } + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public void clear() { + + } + + static long markRequestedOnce(ZipCoordinator instance) { + for (;;) { + final long state = instance.state; + + if (isInterrupted(state) || isRequestedOnce(state)) { + return state; + } + + final long nextState = state | REQUESTED_ONCE_FLAG; + if (STATE.compareAndSet(instance, state, nextState)) { + return state; + } + } + } + + static long markDeliveredSignal(ZipCoordinator instance) { + final int n = instance.subscribers.length; + for (;;) { + final long state = instance.state; + + if (isInterrupted(state) || n == deliveredSignals(state)) { + return state; + } + + final long nextState = state + 1; + if (STATE.compareAndSet(instance, state, nextState)) { + return state; + } + } + } + + static long markForceTerminated(ZipCoordinator instance) { + final int n = instance.subscribers.length; + for (;;) { + final long state = instance.state; + + if (isInterrupted(state) || n == deliveredSignals(state)) { + return state; + } + + final long nextState = (state &~ MAX_SIGNALS_VALUE) | n | INTERRUPTED_FLAG; + if (STATE.compareAndSet(instance, state, nextState)) { + return state; + } + } + } + + static long markInterrupted(ZipCoordinator instance) { + final int n = instance.subscribers.length; + for (;;) { + final long state = instance.state; + + if (isInterrupted(state) || n == deliveredSignals(state)) { + return state; + } + + final long nextState = state | INTERRUPTED_FLAG; + if (STATE.compareAndSet(instance, state, nextState)) { + return state; + } + } + } + + static boolean isRequestedOnce(long state) { + return (state & REQUESTED_ONCE_FLAG) == REQUESTED_ONCE_FLAG; + } + + static int deliveredSignals(long state) { + return (int) (state & Integer.MAX_VALUE); + } + + static boolean isInterrupted(long state) { + return (state & INTERRUPTED_FLAG) == INTERRUPTED_FLAG; + } } static final class ZipInner implements InnerConsumer { @@ -281,9 +424,7 @@ volatile Subscription s; @SuppressWarnings("rawtypes") static final AtomicReferenceFieldUpdater S = - AtomicReferenceFieldUpdater.newUpdater(ZipInner.class, - Subscription.class, - "s"); + AtomicReferenceFieldUpdater.newUpdater(ZipInner.class, Subscription.class, "s"); Object value; Throwable error; @@ -316,60 +457,91 @@ @Override public Context currentContext() { - return parent.currentContext(); + return parent.actual.currentContext(); } @Override public void onSubscribe(Subscription s) { if (Operators.setOnce(S, this, s)) { s.request(Long.MAX_VALUE); } - else { - s.cancel(); - } } @Override public void onNext(Object t) { if (value == null) { value = t; parent.signal(); + /* + We use cancelledSubscription() as a marker to detect whether a value is present in the cancelAll/cancelExcept parent phase. + This is done in order to deal with a single volatile. + Of course, it could also be set that way due to an early cancellation (in which case we very much want to discard the t). + Note the accumulator should already have seen and used the value in parent.signal(). + We don't really cancel the subscription, but we don't really care because that's a Mono. Having received onNext, we're sure + to get onComplete afterwards. + See also boolean cancel(). + */ + final Subscription a = this.s; + if (a != Operators.cancelledSubscription() && S.compareAndSet(this, a, Operators.cancelledSubscription())) { + return; + } + Operators.onDiscard(t, parent.actual.currentContext()); } } @Override public void onError(Throwable t) { + if (value != null) { + Operators.onErrorDropped(t, parent.actual.currentContext()); + return; + } + error = t; if (parent.delayError) { - parent.signal(); + if (!parent.signal()) { + Operators.onErrorDropped(t, parent.actual.currentContext()); + } } else { - int n = parent.subscribers.length; - if (ZipCoordinator.DONE.getAndSet(parent, n) != n) { - parent.cancelExcept(this); - parent.actual.onError(t); + final long previousState = ZipCoordinator.markForceTerminated(parent); + if (ZipCoordinator.isInterrupted(previousState)) { + return; } + + parent.cancelExcept(this); + parent.actual.onError(t); } } @Override public void onComplete() { - if (value == null) { - if (parent.delayError) { - parent.signal(); + if (value != null) { + return; + } + + if (parent.delayError) { + parent.signal(); + } + else { + final long previousState = ZipCoordinator.markForceTerminated(parent); + if (ZipCoordinator.isInterrupted(previousState)) { + return; } - else { - int n = parent.subscribers.length; - if (ZipCoordinator.DONE.getAndSet(parent, n) != n) { - parent.cancelExcept(this); - parent.actual.onComplete(); - } - } + + parent.cancelExcept(this); + parent.actual.onComplete(); } } - void cancel() { - Operators.terminate(S, this); + boolean cancel() { + /* + If S == cancelledSubscription, it means we've either already cancelled (nothing to do) or previously signalled an onNext. + In both cases, terminate will return false (having failed to swap to cancelledSubscription) and the method will return true. + This is to be interpreted by parent callers (cancelAll/cancelExcept) as an indicator that a value is likely present, + and that it should be discarded by the parent. Parent could try to discard twice, in the case of double cancellation, but + discard should be idempotent. + */ + return !Operators.terminate(S, this); } } } Index: 3rdParty_sources/reactor/reactor/core/publisher/NextProcessor.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/NextProcessor.java (.../NextProcessor.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/NextProcessor.java (.../NextProcessor.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import java.util.stream.Stream; import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; import reactor.core.CorePublisher; @@ -37,7 +38,7 @@ // NextProcessor extends a deprecated class but is itself not deprecated and is here to stay, hence the following line is ok. @SuppressWarnings("deprecation") -class NextProcessor extends MonoProcessor { +class NextProcessor extends MonoProcessor implements CoreSubscriber, reactor.core.Disposable, Scannable { /** * This boolean indicates a usage as `Mono#share()` where, for alignment with Flux#share(), the removal of all @@ -47,6 +48,42 @@ volatile NextInner[] subscribers; + /** + * Block the calling thread indefinitely, waiting for the completion of this {@link NextProcessor}. If the + * {@link NextProcessor} is completed with an error a RuntimeException that wraps the error is thrown. + * + * @return the value of this {@link NextProcessor} + */ + @Override + @Nullable + public O block() { + return block(null); + } + + @Override + public boolean isDisposed() { + return isTerminated(); + } + + //TODO reintroduce the boolean getters below once MonoProcessor is removed again +// /** +// * Indicates whether this {@link NextProcessor} has been completed with an error. +// * +// * @return {@code true} if this {@link NextProcessor} was completed with an error, {@code false} otherwise. +// */ +// public final boolean isError() { +// return getError() != null; +// } +// +// /** +// * Indicates whether this {@link NextProcessor} has been successfully completed a value. +// * +// * @return {@code true} if this {@link NextProcessor} is successful, {@code false} otherwise. +// */ +// public final boolean isSuccess() { +// return isTerminated() && !isError(); +// } + @SuppressWarnings("rawtypes") static final AtomicReferenceFieldUpdater SUBSCRIBERS = AtomicReferenceFieldUpdater.newUpdater(NextProcessor.class, NextInner[].class, "subscribers"); @@ -82,6 +119,17 @@ SUBSCRIBERS.lazySet(this, source != null ? EMPTY_WITH_SOURCE : EMPTY); } + /** + * For testing purpose. + *

      + * Returns the value that completed this {@link NextProcessor}. Returns {@code null} if the {@link NextProcessor} has not been completed. If the + * {@link NextProcessor} is completed with an error a RuntimeException that wraps the error is thrown. + * + * @return the value that completed the {@link NextProcessor}, or {@code null} if it has not been completed + * + * @throws RuntimeException if the {@link NextProcessor} was completed with an error + */ + @Nullable @Override public O peek() { if (!isTerminated()) { @@ -101,6 +149,15 @@ return null; } + /** + * Block the calling thread for the specified time, waiting for the completion of this {@link NextProcessor}. If the + * {@link NextProcessor} is completed with an error a RuntimeException that wraps the error is thrown. + * + * @param timeout the timeout value as a {@link Duration} + * + * @return the value of this {@link NextProcessor} or {@code null} if the timeout is reached and the {@link NextProcessor} has + * not completed + */ @Override @Nullable public O block(@Nullable Duration timeout) { @@ -128,7 +185,7 @@ return value; } if (timeout != null && delay < System.nanoTime()) { - cancel(); + doCancel(); throw new IllegalStateException("Timeout on Mono blocking read"); } @@ -150,9 +207,9 @@ @SuppressWarnings("unused") EmitResult emitResult = tryEmitValue(null); } - void emitEmpty(Sinks.EmitFailureHandler failureHandler) { + void emitEmpty(EmitFailureHandler failureHandler) { for (;;) { - Sinks.EmitResult emitResult = tryEmitValue(null); + EmitResult emitResult = tryEmitValue(null); if (emitResult.isSuccess()) { return; } @@ -212,7 +269,7 @@ } @SuppressWarnings("unchecked") - Sinks.EmitResult tryEmitError(Throwable cause) { + EmitResult tryEmitError(Throwable cause) { Objects.requireNonNull(cause, "onError cannot be null"); if (UPSTREAM.getAndSet(this, Operators.cancelledSubscription()) == Operators.cancelledSubscription()) { @@ -304,18 +361,33 @@ return EmitResult.OK; } + @Nullable @Override public Object scanUnsafe(Attr key) { if (key == Attr.PARENT) return subscription; - return super.scanUnsafe(key); + //touch guard + boolean t = isTerminated(); + + if (key == Attr.TERMINATED) return t; + if (key == Attr.CANCELLED) return !t && subscription == Operators.cancelledSubscription(); + if (key == Attr.ERROR) return getError(); + if (key == Attr.PREFETCH) return Integer.MAX_VALUE; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; } @Override public Context currentContext() { return Operators.multiSubscribersContext(subscribers); } + /** + * Return the number of active {@link Subscriber} or {@literal -1} if untracked. + * + * @return the number of active {@link Subscriber} or {@literal -1} if untracked + */ @Override public long downstreamCount() { return subscribers.length; @@ -351,6 +423,10 @@ // This method is inherited from a deprecated class and will be removed in 3.5. @SuppressWarnings("deprecation") public void cancel() { + doCancel(); + } + + void doCancel() { //TODO compare with the cancellation in remove(), do we need both approaches? if (isTerminated()) { return; } @@ -380,11 +456,22 @@ return subscription == Operators.cancelledSubscription() && !isTerminated(); } + /** + * Indicates whether this {@link NextProcessor} has been terminated by the + * source producer with a success or an error. + * + * @return {@code true} if this {@link NextProcessor} is successful, {@code false} otherwise. + */ @Override public boolean isTerminated() { return subscribers == TERMINATED; } + /** + * Return the produced {@link Throwable} error if any or null + * + * @return the produced {@link Throwable} error if any or null + */ @Nullable @Override public Throwable getError() { @@ -541,4 +628,4 @@ return super.scanUnsafe(key); } } -} +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/Operators.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/Operators.java (.../Operators.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/Operators.java (.../Operators.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -582,7 +582,7 @@ } /** - * Invoke a (local or global) hook that processes elements that remains in an {@link java.util.Iterator}. + * Invoke a (local or global) hook that processes elements that remains in an {@link Iterator}. * Since iterators can be infinite, this method requires that you explicitly ensure the iterator is * {@code knownToBeFinite}. Typically, operating on an {@link Iterable} one can get such a * guarantee by looking at the {@link Iterable#spliterator() Spliterator's} {@link Spliterator#getExactSizeIfKnown()}. @@ -618,11 +618,48 @@ } } + /** + * Invoke a (local or global) hook that processes elements that remains in an {@link Spliterator}. + * Since spliterators can be infinite, this method requires that you explicitly ensure the spliterator is + * {@code knownToBeFinite}. Typically, one can get such a guarantee by looking at the {@link Spliterator#getExactSizeIfKnown()}. + * + * @param multiple the {@link Spliterator} whose remainder to discard + * @param knownToBeFinite is the caller guaranteeing that the iterator is finite and can be iterated over + * @param context the {@link Context} in which to look for local hook + * @see #onDiscard(Object, Context) + * @see #onDiscardMultiple(Collection, Context) + * @see #onDiscardQueueWithClear(Queue, Context, Function) + */ + public static void onDiscardMultiple(@Nullable Spliterator multiple, boolean knownToBeFinite, Context context) { + if (multiple == null) return; + if (!knownToBeFinite) return; + + Consumer hook = context.getOrDefault(Hooks.KEY_ON_DISCARD, null); + if (hook != null) { + try { + multiple.forEachRemaining(o -> { + if (o != null) { + try { + hook.accept(o); + } + catch (Throwable t) { + log.warn("Error while discarding element from an Spliterator, continuing with next element", t); + } + } + }); + } + catch (Throwable t) { + log.warn("Error while discarding Spliterator, stopping", t); + } + } + } + + /** * An unexpected exception is about to be dropped. *

      * If no hook is registered for {@link Hooks#onErrorDropped(Consumer)}, the dropped - * error is logged at ERROR level and thrown (via {@link Exceptions#bubble(Throwable)}. + * error is logged at ERROR level. * * @param e the dropped exception * @param context a context that might hold a local error consumer @@ -746,7 +783,7 @@ * operator. This exception denotes that an execution was rejected by a * {@link reactor.core.scheduler.Scheduler}, notably when it was already disposed. *

      - * Wrapping is done by calling both {@link Exceptions#bubble(Throwable)} and + * Wrapping is done by calling both {@link Exceptions#failWithRejected(Throwable)} and * {@link #onOperatorError(Subscription, Throwable, Object, Context)}. * * @param original the original execution error @@ -1114,7 +1151,7 @@ T value, String stepName){ return new ScalarSubscription<>(subscriber, value, stepName); } - + /** * Safely gate a {@link Subscriber} by making sure onNext signals are delivered * sequentially (serialized). @@ -1304,16 +1341,16 @@ } /** - * If the actual {@link CoreSubscriber} is not {@link reactor.core.Fuseable.ConditionalSubscriber}, + * If the actual {@link CoreSubscriber} is not {@link Fuseable.ConditionalSubscriber}, * it will apply an adapter which directly maps all - * {@link reactor.core.Fuseable.ConditionalSubscriber#tryOnNext(Object)} to + * {@link Fuseable.ConditionalSubscriber#tryOnNext(Object)} to * {@link Subscriber#onNext(Object)} * and always returns true as the result * * @param passed subscriber type * * @param actual the {@link Subscriber} to adapt - * @return a potentially adapted {@link reactor.core.Fuseable.ConditionalSubscriber} + * @return a potentially adapted {@link Fuseable.ConditionalSubscriber} */ @SuppressWarnings("unchecked") public static Fuseable.ConditionalSubscriber toConditionalSubscriber(CoreSubscriber actual) { @@ -1732,14 +1769,13 @@ /** * A Subscriber/Subscription barrier that holds a single value at most and properly gates asynchronous behaviors * resulting from concurrent request or cancel and onXXX signals. - * Publisher Operators using this Subscriber can be fused (implement Fuseable). * * @param The upstream sequence type * @param The downstream sequence type */ public static class MonoSubscriber implements InnerOperator, - Fuseable, //for constants only + Fuseable, QueueSubscription { protected final CoreSubscriber actual; @@ -1776,7 +1812,6 @@ @Override public final void clear() { - STATE.lazySet(this, FUSED_CONSUMED); this.value = null; } @@ -1790,18 +1825,6 @@ public final void complete(@Nullable O v) { for (; ; ) { int state = this.state; - if (state == FUSED_EMPTY) { - setValue(v); - //sync memory since setValue is non volatile - if (STATE.compareAndSet(this, FUSED_EMPTY, FUSED_READY)) { - Subscriber a = actual; - a.onNext(v); - a.onComplete(); - return; - } - //refresh state if race occurred so we test if cancelled in the next comparison - state = this.state; - } // if state is >= HAS_CANCELLED or bit zero is set (*_HAS_VALUE) case, return if ((state & ~HAS_REQUEST_NO_VALUE) != 0) { @@ -1850,7 +1873,7 @@ @Override public final boolean isEmpty() { - return this.state != FUSED_READY; + return true; } @Override @@ -1877,11 +1900,6 @@ @Override @Nullable public final O poll() { - if (STATE.compareAndSet(this, FUSED_READY, FUSED_CONSUMED)) { - O v = value; - value = null; - return v; - } return null; } @@ -1893,8 +1911,7 @@ if (s == CANCELLED) { return; } - // if the any bits 1-31 are set, we are either in fusion mode (FUSED_*) - // or request has been called (HAS_REQUEST_*) + // if any bits 1-31 are set, request(n) has been called (HAS_REQUEST_*) if ((s & ~NO_REQUEST_HAS_VALUE) != 0) { return; } @@ -1917,10 +1934,6 @@ @Override public int requestFusion(int mode) { - if ((mode & ASYNC) != 0) { - STATE.lazySet(this, FUSED_EMPTY); - return ASYNC; - } return NONE; } @@ -1963,25 +1976,159 @@ /** * Indicates the Subscription has been cancelled. */ - static final int CANCELLED = 4; - /** - * Indicates this Subscription is in fusion mode and is currently empty. - */ - static final int FUSED_EMPTY = 8; - /** - * Indicates this Subscription is in fusion mode and has a value. - */ - static final int FUSED_READY = 16; - /** - * Indicates this Subscription is in fusion mode and its value has been consumed. - */ - static final int FUSED_CONSUMED = 32; + static final int CANCELLED = 4; + @SuppressWarnings("rawtypes") - static final AtomicIntegerFieldUpdater STATE = + static final AtomicIntegerFieldUpdater STATE = AtomicIntegerFieldUpdater.newUpdater(MonoSubscriber.class, "state"); } + static abstract class BaseFluxToMonoOperator implements InnerOperator, + Fuseable, + QueueSubscription { + final CoreSubscriber actual; + + Subscription s; + + boolean hasRequest; + + volatile int state; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater STATE = + AtomicIntegerFieldUpdater.newUpdater(BaseFluxToMonoOperator.class, "state"); + + BaseFluxToMonoOperator(CoreSubscriber actual) { + this.actual = actual; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PREFETCH) return 0; + if (key == Attr.PARENT) return s; + if (key == Attr.RUN_STYLE) return RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public final CoreSubscriber actual() { + return this.actual; + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + + actual.onSubscribe(this); + } + } + + @Override + public void request(long n) { + if (!hasRequest) { + hasRequest = true; + + final int state = this.state; + if ((state & 1) == 1) { + return; + } + + if (STATE.compareAndSet(this, state, state | 1)) { + if (state == 0) { + s.request(Long.MAX_VALUE); + } + else { + // completed before request means source was empty + final O value = accumulatedValue(); + + if (value == null) { + return; + } + + this.actual.onNext(value); + this.actual.onComplete(); + } + } + } + } + + @Override + public void cancel() { + s.cancel(); + } + + final void completePossiblyEmpty() { + if (hasRequest) { + final O value = accumulatedValue(); + + if (value == null) { + return; + } + + this.actual.onNext(value); + this.actual.onComplete(); + return; + } + + final int state = this.state; + if (state == 0 && STATE.compareAndSet(this, 0, 2)) { + return; + } + + final O value = accumulatedValue(); + + if (value == null) { + return; + } + + this.actual.onNext(value); + this.actual.onComplete(); + } + + /** + * This method is being called either during onComplete invocation in case request + * has happened before, or during request invocation in case onComplete with no + * values has happened before. + *

      + * Note, this method expectedly returns null if cancellation happened + * before + *

      + * + * @return accumulated/default value or null if cancelled before + */ + @Nullable + abstract O accumulatedValue(); + + @Override + public final I poll() { + return null; + } + + @Override + public final int requestFusion(int requestedMode) { + return Fuseable.NONE; + } + + @Override + public final int size() { + return 0; + } + + @Override + public final boolean isEmpty() { + return true; + } + + @Override + public final void clear() { + + } + } + + /** * A subscription implementation that arbitrates request amounts between subsequent Subscriptions, including the * duration until the first Subscription is set. @@ -2284,7 +2431,7 @@ } } else if (mr != 0L && a != null) { requestAmount = addCap(requestAmount, mr); - alreadyInRequestAmount += mr; + alreadyInRequestAmount += mr; requestTarget = a; } } @@ -2458,7 +2605,7 @@ /** * This class wraps any non-conditional {@link CoreSubscriber} so the delegate - * can have an emulation of {@link reactor.core.Fuseable.ConditionalSubscriber} + * can have an emulation of {@link Fuseable.ConditionalSubscriber} * behaviors * * @param passed subscriber type @@ -2825,4 +2972,4 @@ final static Logger log = Loggers.getLogger(Operators.class); -} +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/ParallelCollect.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/ParallelCollect.java (.../ParallelCollect.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/ParallelCollect.java (.../ParallelCollect.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,6 @@ import java.util.function.Supplier; import org.reactivestreams.Subscriber; -import org.reactivestreams.Subscription; import reactor.core.CoreSubscriber; import reactor.core.Fuseable; import reactor.core.Scannable; @@ -107,14 +106,12 @@ } static final class ParallelCollectSubscriber - extends Operators.MonoSubscriber { + extends Operators.BaseFluxToMonoOperator { final BiConsumer collector; C collection; - Subscription s; - boolean done; ParallelCollectSubscriber(CoreSubscriber subscriber, @@ -126,28 +123,22 @@ } @Override - public void onSubscribe(Subscription s) { - if (Operators.validate(this.s, s)) { - this.s = s; - - actual.onSubscribe(this); - - s.request(Long.MAX_VALUE); - } - } - - @Override public void onNext(T t) { if (done) { Operators.onNextDropped(t, actual.currentContext()); return; } try { - collector.accept(collection, t); + synchronized (this) { + final C collection = this.collection; + if (collection != null) { + collector.accept(collection, t); + } + } } catch (Throwable ex) { - onError(Operators.onOperatorError(this, ex, t, actual.currentContext())); + onError(Operators.onOperatorError(this.s, ex, t, actual.currentContext())); } } @@ -158,7 +149,19 @@ return; } done = true; - collection = null; + + final C c; + synchronized (this) { + c = collection; + collection = null; + } + + if (c == null) { + return; + } + + Operators.onDiscard(c, actual.currentContext()); + actual.onError(t); } @@ -168,20 +171,40 @@ return; } done = true; - C c = collection; - collection = null; - complete(c); + + completePossiblyEmpty(); } @Override public void cancel() { - super.cancel(); s.cancel(); + + final C c; + synchronized (this) { + c = collection; + collection = null; + } + + if (c != null) { + Operators.onDiscard(c, actual.currentContext()); + } } @Override + C accumulatedValue() { + final C c; + synchronized (this) { + c = collection; + collection = null; + } + return c; + } + + @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.TERMINATED) return done; + if (key == Attr.CANCELLED) return collection == null && !done; return super.scanUnsafe(key); } } Index: 3rdParty_sources/reactor/reactor/core/publisher/ParallelFluxName.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/ParallelFluxName.java (.../ParallelFluxName.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/ParallelFluxName.java (.../ParallelFluxName.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,9 +17,9 @@ package reactor.core.publisher; import java.util.Collections; -import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; import java.util.Objects; -import java.util.Set; import reactor.core.CoreSubscriber; import reactor.core.Scannable; @@ -42,15 +42,15 @@ final String name; - final Set> tags; + final List> tagsWithDuplicates; @SuppressWarnings("unchecked") static ParallelFlux createOrAppend(ParallelFlux source, String name) { Objects.requireNonNull(name, "name"); if (source instanceof ParallelFluxName) { ParallelFluxName s = (ParallelFluxName) source; - return new ParallelFluxName<>(s.source, name, s.tags); + return new ParallelFluxName<>(s.source, name, s.tagsWithDuplicates); } return new ParallelFluxName<>(source, name, null); } @@ -60,25 +60,29 @@ Objects.requireNonNull(tagName, "tagName"); Objects.requireNonNull(tagValue, "tagValue"); - Set> tags = Collections.singleton(Tuples.of(tagName, tagValue)); + Tuple2 newTag = Tuples.of(tagName, tagValue); if (source instanceof ParallelFluxName) { ParallelFluxName s = (ParallelFluxName) source; - if(s.tags != null) { - tags = new HashSet<>(tags); - tags.addAll(s.tags); + List> tags; + if(s.tagsWithDuplicates != null) { + tags = new LinkedList<>(s.tagsWithDuplicates); + tags.add(newTag); } + else { + tags = Collections.singletonList(newTag); + } return new ParallelFluxName<>(s.source, s.name, tags); } - return new ParallelFluxName<>(source, null, tags); + return new ParallelFluxName<>(source, null, Collections.singletonList(newTag)); } ParallelFluxName(ParallelFlux source, @Nullable String name, - @Nullable Set> tags) { + @Nullable List> tags) { this.source = source; this.name = name; - this.tags = tags; + this.tagsWithDuplicates = tags; } @Override @@ -98,8 +102,8 @@ return name; } - if (key == Attr.TAGS && tags != null) { - return tags.stream(); + if (key == Attr.TAGS && tagsWithDuplicates != null) { + return tagsWithDuplicates.stream(); } if (key == Attr.PARENT) return source; Index: 3rdParty_sources/reactor/reactor/core/publisher/ParallelLiftFuseable.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/ParallelLiftFuseable.java (.../ParallelLiftFuseable.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/ParallelLiftFuseable.java (.../ParallelLiftFuseable.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -91,7 +91,7 @@ Objects.requireNonNull(converted, "Lifted subscriber MUST NOT be null"); - if (actual instanceof Fuseable.QueueSubscription + if (actual instanceof QueueSubscription && !(converted instanceof QueueSubscription)) { //user didn't produce a QueueSubscription, original was one converted = new FluxHide.SuppressFuseableSubscriber<>(converted); @@ -105,4 +105,4 @@ } -} +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/ParallelMergeOrdered.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/ParallelMergeOrdered.java (.../ParallelMergeOrdered.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/ParallelMergeOrdered.java (.../ParallelMergeOrdered.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -64,7 +64,7 @@ @Override public void subscribe(CoreSubscriber actual) { FluxMergeComparing.MergeOrderedMainProducer - main = new FluxMergeComparing.MergeOrderedMainProducer<>(actual, valueComparator, prefetch, source.parallelism(), true); + main = new FluxMergeComparing.MergeOrderedMainProducer<>(actual, valueComparator, prefetch, source.parallelism(), true, true); actual.onSubscribe(main); source.subscribe(main.subscribers); } Index: 3rdParty_sources/reactor/reactor/core/publisher/ParallelMergeReduce.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/ParallelMergeReduce.java (.../ParallelMergeReduce.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/ParallelMergeReduce.java (.../ParallelMergeReduce.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -58,24 +58,28 @@ @Override public void subscribe(CoreSubscriber actual) { MergeReduceMain parent = - new MergeReduceMain<>(actual, source.parallelism(), reducer); + new MergeReduceMain<>(source, actual, source.parallelism(), reducer); actual.onSubscribe(parent); - - source.subscribe(parent.subscribers); } static final class MergeReduceMain - extends Operators.MonoSubscriber { + implements InnerProducer, + Fuseable, + QueueSubscription { + final ParallelFlux source; + + final CoreSubscriber actual; + final MergeReduceInner[] subscribers; final BiFunction reducer; volatile SlotPair current; + @SuppressWarnings("rawtypes") - static final AtomicReferenceFieldUpdater - CURRENT = AtomicReferenceFieldUpdater.newUpdater( - MergeReduceMain.class, + static final AtomicReferenceFieldUpdater CURRENT = + AtomicReferenceFieldUpdater.newUpdater(MergeReduceMain.class, SlotPair.class, "current"); @@ -94,28 +98,37 @@ Throwable.class, "error"); - MergeReduceMain(CoreSubscriber subscriber, + MergeReduceMain( + ParallelFlux source, + CoreSubscriber actual, int n, BiFunction reducer) { - super(subscriber); + this.actual = actual; + this.source = source; @SuppressWarnings("unchecked") MergeReduceInner[] a = new MergeReduceInner[n]; for (int i = 0; i < n; i++) { a[i] = new MergeReduceInner<>(this, reducer); } this.subscribers = a; this.reducer = reducer; - REMAINING.lazySet(this, n); + REMAINING.lazySet(this, n | Integer.MIN_VALUE); } @Override + public CoreSubscriber actual() { + return actual; + } + + @Override @Nullable public Object scanUnsafe(Attr key) { if (key == Attr.ERROR) return error; - if (key == Attr.TERMINATED) return REMAINING.get(this) == 0; + if (key == Attr.TERMINATED) return this.remaining == 0; + if (key == Attr.CANCELLED) return this.remaining == Integer.MIN_VALUE; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return super.scanUnsafe(key); + return InnerProducer.super.scanUnsafe(key); } @Nullable @@ -152,12 +165,26 @@ @Override public void cancel() { - for (MergeReduceInner inner : subscribers) { - inner.cancel(); + int r = REMAINING.getAndSet(this, Integer.MIN_VALUE); + if ((r & Integer.MIN_VALUE) != Integer.MIN_VALUE) { + for (MergeReduceInner inner : subscribers) { + inner.cancel(); + } } - super.cancel(); } + @Override + public void request(long n) { + final int r = this.remaining; + if ((r & Integer.MIN_VALUE) != Integer.MIN_VALUE) { + return; + } + + if (REMAINING.compareAndSet(this, r, r & Integer.MAX_VALUE)) { + source.subscribe(subscribers); + } + } + void innerError(Throwable ex) { if(ERROR.compareAndSet(this, null, ex)){ cancel(); @@ -191,18 +218,56 @@ } } - if (REMAINING.decrementAndGet(this) == 0) { - SlotPair sp = current; + if (decrementAndGet(this) == 0) { + final SlotPair sp = current; CURRENT.lazySet(this, null); if (sp != null) { - complete(sp.first); + actual.onNext(sp.first); + actual.onComplete(); } else { actual.onComplete(); } } } + + static int decrementAndGet(MergeReduceMain instance) { + int prev, next; + do { + prev = instance.remaining; + if (prev == Integer.MIN_VALUE) { + return Integer.MIN_VALUE; + } + next = prev - 1; + } while (!REMAINING.compareAndSet(instance, prev, next)); + return next; + } + + @Override + public T poll() { + return null; + } + + @Override + public int requestFusion(int requestedMode) { + return Fuseable.NONE; + } + + @Override + public int size() { + return 0; + } + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public void clear() { + + } } static final class MergeReduceInner implements InnerConsumer { @@ -231,7 +296,7 @@ @Override public Context currentContext() { - return parent.currentContext(); + return parent.actual.currentContext(); } @Override @@ -283,7 +348,7 @@ @Override public void onError(Throwable t) { if (done) { - Operators.onErrorDropped(t, parent.currentContext()); + Operators.onErrorDropped(t, parent.actual.currentContext()); return; } done = true; Index: 3rdParty_sources/reactor/reactor/core/publisher/ParallelReduceSeed.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/ParallelReduceSeed.java (.../ParallelReduceSeed.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/ParallelReduceSeed.java (.../ParallelReduceSeed.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,6 @@ import java.util.function.Supplier; import org.reactivestreams.Subscriber; -import org.reactivestreams.Subscription; import reactor.core.CoreSubscriber; import reactor.core.Fuseable; import reactor.core.Scannable; @@ -52,7 +51,7 @@ @Override @Nullable - public Object scanUnsafe(Scannable.Attr key) { + public Object scanUnsafe(Attr key) { if (key == Attr.PARENT) return source; if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; @@ -107,14 +106,12 @@ static final class ParallelReduceSeedSubscriber - extends Operators.MonoSubscriber { + extends Operators.BaseFluxToMonoOperator { final BiFunction reducer; R accumulator; - Subscription s; - boolean done; ParallelReduceSeedSubscriber(CoreSubscriber subscriber, @@ -126,34 +123,29 @@ } @Override - public void onSubscribe(Subscription s) { - if (Operators.validate(this.s, s)) { - this.s = s; - - actual.onSubscribe(this); - - s.request(Long.MAX_VALUE); - } - } - - @Override public void onNext(T t) { if (done) { Operators.onNextDropped(t, actual.currentContext()); return; } - R v; + synchronized (this) { + R v; + try { + if (accumulator == null) { + return; + } - try { - v = Objects.requireNonNull(reducer.apply(accumulator, t), "The reducer returned a null value"); - } - catch (Throwable ex) { - onError(Operators.onOperatorError(this, ex, t, actual.currentContext())); - return; - } + v = Objects.requireNonNull(reducer.apply(accumulator, t), + "The reducer returned a null value"); + } + catch (Throwable ex) { + onError(Operators.onOperatorError(this.s, ex, t, actual.currentContext())); + return; + } - accumulator = v; + accumulator = v; + } } @Override @@ -163,7 +155,21 @@ return; } done = true; - accumulator = null; + + final R a; + synchronized (this) { + a = accumulator; + if (a != null) { + accumulator = null; + } + } + + if (a == null) { + return; + } + + Operators.onDiscard(a, currentContext()); + actual.onError(t); } @@ -174,21 +180,46 @@ } done = true; - R a = accumulator; - accumulator = null; - complete(a); + completePossiblyEmpty(); } @Override + R accumulatedValue() { + final R a; + synchronized (this) { + a = accumulator; + if (a != null) { + accumulator = null; + } + } + return a; + } + + @Override public void cancel() { - super.cancel(); s.cancel(); + + final R a; + synchronized (this) { + a = accumulator; + if (a != null) { + accumulator = null; + } + } + + if (a == null) { + return; + } + + Operators.onDiscard(a, currentContext()); } @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.TERMINATED) return done; + if (key == Attr.CANCELLED) return !done && accumulator == null; return super.scanUnsafe(key); } } -} +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/ParallelSource.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/ParallelSource.java (.../ParallelSource.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/ParallelSource.java (.../ParallelSource.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -72,7 +72,7 @@ @Override @Nullable - public Object scanUnsafe(Scannable.Attr key) { + public Object scanUnsafe(Attr key) { if (key == Attr.PARENT) return source; if (key == Attr.PREFETCH) return getPrefetch(); if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; @@ -518,4 +518,4 @@ } } } -} +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/ParallelThen.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/ParallelThen.java (.../ParallelThen.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/ParallelThen.java (.../ParallelThen.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2019-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ package reactor.core.publisher; -import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import org.reactivestreams.Subscription; @@ -48,75 +48,201 @@ @Override public void subscribe(CoreSubscriber actual) { - ThenMain parent = new ThenMain(actual, source.parallelism()); + ThenMain parent = new ThenMain(actual, source); actual.onSubscribe(parent); - - source.subscribe(parent.subscribers); } - static final class ThenMain - extends Operators.MonoSubscriber { + static final class ThenMain implements InnerProducer, + Fuseable, //for constants only + QueueSubscription { final ThenInner[] subscribers; + final CoreSubscriber actual; + final ParallelFlux source; - volatile int remaining; - @SuppressWarnings("rawtypes") - static final AtomicIntegerFieldUpdater - REMAINING = AtomicIntegerFieldUpdater.newUpdater( - ThenMain.class, - "remaining"); + volatile long state; - volatile Throwable error; @SuppressWarnings("rawtypes") - static final AtomicReferenceFieldUpdater - ERROR = AtomicReferenceFieldUpdater.newUpdater( - ThenMain.class, - Throwable.class, - "error"); + static final AtomicLongFieldUpdater STATE = + AtomicLongFieldUpdater.newUpdater(ThenMain.class, "state"); - ThenMain(CoreSubscriber subscriber, int n) { - super(subscriber); + + static final long CANCELLED_FLAG = + 0b1000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000L; + static final long REQUESTED_FLAG = + 0b0100_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000L; + + static final long INNER_COMPLETED_MAX = + 0b0000_0000_0000_0000_0000_0000_0000_0000_0111_1111_1111_1111_1111_1111_1111_1111L; + + ThenMain(CoreSubscriber actual, final ParallelFlux source) { + this.actual = actual; + this.source = source; + + final int n = source.parallelism(); ThenInner[] a = new ThenInner[n]; for (int i = 0; i < n; i++) { a[i] = new ThenInner(this); } this.subscribers = a; - REMAINING.lazySet(this, n); } @Override + public CoreSubscriber actual() { + return this.actual; + } + + @Override @Nullable public Object scanUnsafe(Attr key) { - if (key == Attr.ERROR) return error; - if (key == Attr.TERMINATED) return REMAINING.get(this) == 0; + if (key == Attr.TERMINATED) return innersCompletedCount(this.state) == source.parallelism(); + if (key == Attr.CANCELLED) return isCancelled(this.state) && innersCompletedCount(this.state) != source.parallelism(); + if (key == Attr.PREFETCH) return 0; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; - return super.scanUnsafe(key); + return InnerProducer.super.scanUnsafe(key); } @Override public void cancel() { + final long previousState = markCancelled(this); + + if (isCancelled(previousState) || !isRequestedOnce(previousState)) { + return; + } + for (ThenInner inner : subscribers) { inner.cancel(); } - super.cancel(); } - void innerError(Throwable ex) { - if(ERROR.compareAndSet(this, null, ex)){ - cancel(); - actual.onError(ex); + @Override + public void request(long n) { + if (!STATE.compareAndSet(this, 0, REQUESTED_FLAG)) { + return; } - else if(error != ex) { - Operators.onErrorDropped(ex, actual.currentContext()); + + source.subscribe(this.subscribers); + } + + void innerError(Throwable ex, ThenInner innerCaller) { + final long previousState = markForceTerminated(this); + + final int n = this.source.parallelism(); + + if (isCancelled(previousState) || innersCompletedCount(previousState) == n) { + return; } + + for (ThenInner inner : subscribers) { + if (inner != innerCaller) { + inner.cancel(); + } + } + + actual.onError(ex); } void innerComplete() { - if (REMAINING.decrementAndGet(this) == 0) { + final long previousState = markInnerCompleted(this); + + final int n = this.source.parallelism(); + final int innersCompletedCount = innersCompletedCount(previousState); + + if (isCancelled(previousState) || innersCompletedCount == n) { + return; + } + + if ((innersCompletedCount + 1) == n) { actual.onComplete(); } } + + @Override + public int requestFusion(int requestedMode) { + return Fuseable.NONE; + } + + @Override + public Void poll() { + return null; + } + + @Override + public int size() { + return 0; + } + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public void clear() { + + } + + static long markForceTerminated(ThenMain instance) { + final int n = instance.source.parallelism(); + for (;;) { + final long state = instance.state; + + if (isCancelled(state) || innersCompletedCount(state) == n) { + return state; + } + + final long nextState = (state & ~INNER_COMPLETED_MAX) | CANCELLED_FLAG | n; + if (STATE.compareAndSet(instance, state, nextState)) { + return state; + } + } + } + + static boolean isRequestedOnce(long state) { + return (state & REQUESTED_FLAG) == REQUESTED_FLAG; + } + + static long markCancelled(ThenMain instance) { + final int n = instance.source.parallelism(); + for(;;) { + final long state = instance.state; + + if (isCancelled(state) || innersCompletedCount(state) == n) { + return state; + } + + final long nextState = state | CANCELLED_FLAG; + if (STATE.weakCompareAndSet(instance, state, nextState)) { + return state; + } + } + } + + static boolean isCancelled(long state) { + return (state & CANCELLED_FLAG) == CANCELLED_FLAG; + } + + static int innersCompletedCount(long state) { + return (int) (state & INNER_COMPLETED_MAX); + } + + static long markInnerCompleted(ThenMain instance) { + final int n = instance.source.parallelism(); + for (;;) { + final long state = instance.state; + + if (isCancelled(state) || innersCompletedCount(state) == n) { + return state; + } + + final long nextState = state + 1; + if (STATE.compareAndSet(instance, state, nextState)) { + return state; + } + } + } + } static final class ThenInner implements InnerConsumer { @@ -137,7 +263,7 @@ @Override public Context currentContext() { - return parent.currentContext(); + return parent.actual.currentContext(); } @Override @@ -162,12 +288,12 @@ @Override public void onNext(Object t) { //ignored - Operators.onDiscard(t, parent.currentContext()); + Operators.onDiscard(t, parent.actual.currentContext()); } @Override public void onError(Throwable t) { - parent.innerError(t); + parent.innerError(t, this); } @Override Index: 3rdParty_sources/reactor/reactor/core/publisher/QueueDrainSubscriber.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/QueueDrainSubscriber.java (.../QueueDrainSubscriber.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/QueueDrainSubscriber.java (.../QueueDrainSubscriber.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2018-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -284,9 +284,24 @@ //------------------------------------------------------------------- /** Pads the header away from other fields. */ +@SuppressWarnings("unused") class QueueDrainSubscriberPad0 { - volatile long p1, p2, p3, p4, p5, p6, p7; - volatile long p8, p9, p10, p11, p12, p13, p14, p15; + byte pad000,pad001,pad002,pad003,pad004,pad005,pad006,pad007;// 8b + byte pad010,pad011,pad012,pad013,pad014,pad015,pad016,pad017;// 16b + byte pad020,pad021,pad022,pad023,pad024,pad025,pad026,pad027;// 24b + byte pad030,pad031,pad032,pad033,pad034,pad035,pad036,pad037;// 32b + byte pad040,pad041,pad042,pad043,pad044,pad045,pad046,pad047;// 40b + byte pad050,pad051,pad052,pad053,pad054,pad055,pad056,pad057;// 48b + byte pad060,pad061,pad062,pad063,pad064,pad065,pad066,pad067;// 56b + byte pad070,pad071,pad072,pad073,pad074,pad075,pad076,pad077;// 64b + byte pad100,pad101,pad102,pad103,pad104,pad105,pad106,pad107;// 72b + byte pad110,pad111,pad112,pad113,pad114,pad115,pad116,pad117;// 80b + byte pad120,pad121,pad122,pad123,pad124,pad125,pad126,pad127;// 88b + byte pad130,pad131,pad132,pad133,pad134,pad135,pad136,pad137;// 96b + byte pad140,pad141,pad142,pad143,pad144,pad145,pad146,pad147;//104b + byte pad150,pad151,pad152,pad153,pad154,pad155,pad156,pad157;//112b + byte pad160,pad161,pad162,pad163,pad164,pad165,pad166,pad167;//120b + byte pad170,pad171,pad172,pad173,pad174,pad175,pad176,pad177;//128b } /** The WIP counter. */ @@ -295,9 +310,24 @@ } /** Pads away the wip from the other fields. */ +@SuppressWarnings("unused") class QueueDrainSubscriberPad2 extends QueueDrainSubscriberWip { - volatile long p1a, p2a, p3a, p4a, p5a, p6a, p7a; - volatile long p8a, p9a, p10a, p11a, p12a, p13a, p14a, p15a; + byte pad000,pad001,pad002,pad003,pad004,pad005,pad006,pad007;// 8b + byte pad010,pad011,pad012,pad013,pad014,pad015,pad016,pad017;// 16b + byte pad020,pad021,pad022,pad023,pad024,pad025,pad026,pad027;// 24b + byte pad030,pad031,pad032,pad033,pad034,pad035,pad036,pad037;// 32b + byte pad040,pad041,pad042,pad043,pad044,pad045,pad046,pad047;// 40b + byte pad050,pad051,pad052,pad053,pad054,pad055,pad056,pad057;// 48b + byte pad060,pad061,pad062,pad063,pad064,pad065,pad066,pad067;// 56b + byte pad070,pad071,pad072,pad073,pad074,pad075,pad076,pad077;// 64b + byte pad100,pad101,pad102,pad103,pad104,pad105,pad106,pad107;// 72b + byte pad110,pad111,pad112,pad113,pad114,pad115,pad116,pad117;// 80b + byte pad120,pad121,pad122,pad123,pad124,pad125,pad126,pad127;// 88b + byte pad130,pad131,pad132,pad133,pad134,pad135,pad136,pad137;// 96b + byte pad140,pad141,pad142,pad143,pad144,pad145,pad146,pad147;//104b + byte pad150,pad151,pad152,pad153,pad154,pad155,pad156,pad157;//112b + byte pad160,pad161,pad162,pad163,pad164,pad165,pad166,pad167;//120b + byte pad170,pad171,pad172,pad173,pad174,pad175,pad176,pad177;//128b } /** Contains the requested field. */ @@ -310,7 +340,22 @@ } /** Pads away the requested from the other fields. */ +@SuppressWarnings("unused") class QueueDrainSubscriberPad4 extends QueueDrainSubscriberPad3 { - volatile long q1, q2, q3, q4, q5, q6, q7; - volatile long q8, q9, q10, q11, q12, q13, q14, q15; + byte pad000,pad001,pad002,pad003,pad004,pad005,pad006,pad007;// 8b + byte pad010,pad011,pad012,pad013,pad014,pad015,pad016,pad017;// 16b + byte pad020,pad021,pad022,pad023,pad024,pad025,pad026,pad027;// 24b + byte pad030,pad031,pad032,pad033,pad034,pad035,pad036,pad037;// 32b + byte pad040,pad041,pad042,pad043,pad044,pad045,pad046,pad047;// 40b + byte pad050,pad051,pad052,pad053,pad054,pad055,pad056,pad057;// 48b + byte pad060,pad061,pad062,pad063,pad064,pad065,pad066,pad067;// 56b + byte pad070,pad071,pad072,pad073,pad074,pad075,pad076,pad077;// 64b + byte pad100,pad101,pad102,pad103,pad104,pad105,pad106,pad107;// 72b + byte pad110,pad111,pad112,pad113,pad114,pad115,pad116,pad117;// 80b + byte pad120,pad121,pad122,pad123,pad124,pad125,pad126,pad127;// 88b + byte pad130,pad131,pad132,pad133,pad134,pad135,pad136,pad137;// 96b + byte pad140,pad141,pad142,pad143,pad144,pad145,pad146,pad147;//104b + byte pad150,pad151,pad152,pad153,pad154,pad155,pad156,pad157;//112b + byte pad160,pad161,pad162,pad163,pad164,pad165,pad166,pad167;//120b + byte pad170,pad171,pad172,pad173,pad174,pad175,pad176,pad177;//128b } \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/ReplayProcessor.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/ReplayProcessor.java (.../ReplayProcessor.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/ReplayProcessor.java (.../ReplayProcessor.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -48,7 +48,7 @@ * * @param the value type * @deprecated To be removed in 3.5, prefer clear cut usage of {@link Sinks} through - * variations under {@link reactor.core.publisher.Sinks.MulticastReplaySpec Sinks.many().replay()}. + * variations under {@link Sinks.MulticastReplaySpec Sinks.many().replay()}. */ @Deprecated public final class ReplayProcessor extends FluxProcessor @@ -446,14 +446,14 @@ @Override public void onComplete() { //no particular error condition handling for onComplete - @SuppressWarnings("unused") Sinks.EmitResult emitResult = tryEmitComplete(); + @SuppressWarnings("unused") EmitResult emitResult = tryEmitComplete(); } @Override - public Sinks.EmitResult tryEmitComplete() { + public EmitResult tryEmitComplete() { FluxReplay.ReplayBuffer b = buffer; if (b.isDone()) { - return Sinks.EmitResult.FAIL_TERMINATED; + return EmitResult.FAIL_TERMINATED; } b.onComplete(); @@ -496,18 +496,18 @@ } @Override - public Sinks.EmitResult tryEmitNext(T t) { + public EmitResult tryEmitNext(T t) { FluxReplay.ReplayBuffer b = buffer; if (b.isDone()) { - return Sinks.EmitResult.FAIL_TERMINATED; + return EmitResult.FAIL_TERMINATED; } //note: ReplayProcessor can so far ALWAYS buffer the element, no FAIL_ZERO_SUBSCRIBER here b.add(t); for (FluxReplay.ReplaySubscription rs : subscribers) { b.replay(rs); } - return Sinks.EmitResult.OK; + return EmitResult.OK; } @Override Index: 3rdParty_sources/reactor/reactor/core/publisher/Signal.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/Signal.java (.../Signal.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/Signal.java (.../Signal.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -229,18 +229,6 @@ SignalType getType(); /** - * Return the readonly {@link Context} that is accessible by the time this {@link Signal} was - * emitted. - * - * @return an immutable {@link Context}, or an empty one if no context is available. - * @deprecated use {@link #getContextView()} instead. To be removed in 3.5.0 - */ - @Deprecated - default Context getContext() { - return Context.of(getContextView()); - } - - /** * Return the readonly {@link ContextView} that is accessible by the time this {@link Signal} was * emitted. * Index: 3rdParty_sources/reactor/reactor/core/publisher/SinkEmptyMulticast.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/SinkEmptyMulticast.java (.../SinkEmptyMulticast.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/SinkEmptyMulticast.java (.../SinkEmptyMulticast.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2020-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,10 +37,15 @@ @SuppressWarnings("rawtypes") static final Inner[] EMPTY = new Inner[0]; - @SuppressWarnings("rawtypes") - static final Inner[] TERMINATED = new Inner[0]; + static final Inner[] TERMINATED_EMPTY = new Inner[0]; + @SuppressWarnings("rawtypes") + static final Inner[] TERMINATED_ERROR = new Inner[0]; + static final int STATE_ADDED = 0; + static final int STATE_ERROR = -1; + static final int STATE_EMPTY = -2; + @Nullable Throwable error; @@ -58,12 +63,23 @@ return this; } + boolean isTerminated(Inner[] array) { + return array == TERMINATED_EMPTY || array == TERMINATED_ERROR; + } + @Override public EmitResult tryEmitEmpty() { - Inner[] array = SUBSCRIBERS.getAndSet(this, TERMINATED); + Inner[] array; + for (;;) { + array = this.subscribers; - if (array == TERMINATED) { - return Sinks.EmitResult.FAIL_TERMINATED; + if (isTerminated(array)) { + return EmitResult.FAIL_TERMINATED; + } + + if (SUBSCRIBERS.compareAndSet(this, array, TERMINATED_EMPTY)) { + break; + } } for (Inner as : array) { @@ -77,23 +93,36 @@ public EmitResult tryEmitError(Throwable cause) { Objects.requireNonNull(cause, "onError cannot be null"); - Inner[] prevSubscribers = SUBSCRIBERS.getAndSet(this, TERMINATED); - if (prevSubscribers == TERMINATED) { + Inner[] prevSubscribers = this.subscribers; + + if (isTerminated(prevSubscribers)) { return EmitResult.FAIL_TERMINATED; } error = cause; + for (;;) { + if (SUBSCRIBERS.compareAndSet(this, prevSubscribers, TERMINATED_ERROR)) { + break; + } + + prevSubscribers = this.subscribers; + if (isTerminated(prevSubscribers)) { + return EmitResult.FAIL_TERMINATED; + } + } + for (Inner as : prevSubscribers) { as.error(cause); } + return EmitResult.OK; } @Override public Object scanUnsafe(Attr key) { - if (key == Attr.TERMINATED) return subscribers == TERMINATED; - if (key == Attr.ERROR) return error; + if (key == Attr.TERMINATED) return isTerminated(subscribers); + if (key == Attr.ERROR) return subscribers == TERMINATED_ERROR ? error : null; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; return null; @@ -104,21 +133,25 @@ return Operators.multiSubscribersContext(subscribers); } - boolean add(Inner ps) { + int add(Inner ps) { for (; ; ) { Inner[] a = subscribers; - if (a == TERMINATED) { - return false; + if (a == TERMINATED_EMPTY) { + return STATE_EMPTY; } + if (a == TERMINATED_ERROR) { + return STATE_ERROR; + } + int n = a.length; @SuppressWarnings("unchecked") Inner[] b = new Inner[n + 1]; System.arraycopy(a, 0, b, 0, n); b[n] = ps; if (SUBSCRIBERS.compareAndSet(this, a, b)) { - return true; + return STATE_ADDED; } } } @@ -165,20 +198,21 @@ public void subscribe(final CoreSubscriber actual) { Inner as = new VoidInner<>(actual, this); actual.onSubscribe(as); - if (add(as)) { + final int addedState = add(as); + if (addedState == STATE_ADDED) { if (as.isCancelled()) { remove(as); } } - else { + else if (addedState == STATE_ERROR) { Throwable ex = error; - if (ex != null) { - actual.onError(ex); - } - else { - as.complete(); - } + + actual.onError(ex); } + else { + as.complete(); + } + } @Override Index: 3rdParty_sources/reactor/reactor/core/publisher/SinkManyBestEffort.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/SinkManyBestEffort.java (.../SinkManyBestEffort.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/SinkManyBestEffort.java (.../SinkManyBestEffort.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2020-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,8 +33,7 @@ * @author Simon Baslé */ final class SinkManyBestEffort extends Flux - implements InternalManySink, Scannable, - DirectInnerContainer { + implements InternalManySink, Scannable, DirectInnerContainer { static final DirectInner[] EMPTY = new DirectInner[0]; static final DirectInner[] TERMINATED = new DirectInner[0]; @@ -215,7 +214,13 @@ } } - @Override + /** + * Add a new {@link DirectInner} to this publisher. + * + * @param s the new {@link DirectInner} to add + * + * @return {@code true} if the inner could be added, {@code false} if the publisher cannot accept new subscribers + */ public boolean add(DirectInner s) { DirectInner[] a = subscribers; if (a == TERMINATED) { @@ -240,8 +245,13 @@ } } + /** + * Remove an {@link DirectInner} from this publisher. Does nothing if the inner is not currently managed + * by the publisher. + * + * @param s the {@link DirectInner} to remove + */ @SuppressWarnings("unchecked") - @Override public void remove(DirectInner s) { DirectInner[] a = subscribers; if (a == TERMINATED || a == EMPTY) { @@ -381,5 +391,4 @@ } } -} - +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/SinkManyEmitterProcessor.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/SinkManyEmitterProcessor.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/SinkManyEmitterProcessor.java (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -0,0 +1,652 @@ +/* + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reactor.core.publisher; + +import java.util.Objects; +import java.util.Queue; +import java.util.concurrent.CancellationException; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.stream.Stream; + +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +import reactor.core.CoreSubscriber; +import reactor.core.Disposable; +import reactor.core.Exceptions; +import reactor.core.Fuseable; +import reactor.core.Scannable; +import reactor.core.publisher.Sinks.EmitResult; +import reactor.util.annotation.Nullable; +import reactor.util.concurrent.Queues; +import reactor.util.context.Context; + +import static reactor.core.publisher.FluxPublish.PublishSubscriber.TERMINATED; + +/** + * An implementation of a {@link Sinks.ManyWithUpstream} implementing + * publish-subscribe with synchronous (thread-stealing and happen-before interactions) + * drain loops. + *

      + * The default {@link #create} factories will only produce the new elements observed in + * the parent sequence after a given {@link Subscriber} is subscribed. + *

      + *

      + * + *

      + * + * @param the input and output value type + * + * @author Stephane Maldini + */ +final class SinkManyEmitterProcessor extends Flux implements InternalManySink, + Sinks.ManyWithUpstream, CoreSubscriber, Scannable, Disposable, ContextHolder { + + @SuppressWarnings("rawtypes") + static final FluxPublish.PubSubInner[] EMPTY = new FluxPublish.PublishInner[0]; + + final int prefetch; + + final boolean autoCancel; + + volatile Subscription s; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater S = + AtomicReferenceFieldUpdater.newUpdater(SinkManyEmitterProcessor.class, + Subscription.class, + "s"); + + volatile FluxPublish.PubSubInner[] subscribers; + + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater + SUBSCRIBERS = AtomicReferenceFieldUpdater.newUpdater(SinkManyEmitterProcessor.class, + FluxPublish.PubSubInner[].class, + "subscribers"); + + volatile EmitterDisposable upstreamDisposable; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater UPSTREAM_DISPOSABLE = + AtomicReferenceFieldUpdater.newUpdater(SinkManyEmitterProcessor.class, EmitterDisposable.class, "upstreamDisposable"); + + + @SuppressWarnings("unused") + volatile int wip; + + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(SinkManyEmitterProcessor.class, "wip"); + + volatile Queue queue; + + int sourceMode; + + volatile boolean done; + + volatile Throwable error; + + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater ERROR = + AtomicReferenceFieldUpdater.newUpdater(SinkManyEmitterProcessor.class, + Throwable.class, + "error"); + + SinkManyEmitterProcessor(boolean autoCancel, int prefetch) { + if (prefetch < 1) { + throw new IllegalArgumentException("bufferSize must be strictly positive, " + "was: " + prefetch); + } + this.autoCancel = autoCancel; + this.prefetch = prefetch; + //doesn't use INIT/CANCELLED distinction, contrary to FluxPublish) + //see remove() + SUBSCRIBERS.lazySet(this, EMPTY); + } + + @Override + public Stream inners() { + return Stream.of(subscribers); + } + + @Override + public Context currentContext() { + return Operators.multiSubscribersContext(subscribers); + } + + + private boolean isDetached() { + return s == Operators.cancelledSubscription() && done && error instanceof CancellationException; + } + + private boolean detach() { + if (Operators.terminate(S, this)) { + done = true; + CancellationException detachException = new CancellationException("the ManyWithUpstream sink had a Subscription to an upstream which has been manually cancelled"); + if (ERROR.compareAndSet(this, null, detachException)) { + Queue q = queue; + if (q != null) { + q.clear(); + } + for (FluxPublish.PubSubInner inner : terminate()) { + inner.actual.onError(detachException); + } + return true; + } + } + return false; + } + + @Override + public Disposable subscribeTo(Publisher upstream) { + EmitterDisposable ed = new EmitterDisposable(this); + if (UPSTREAM_DISPOSABLE.compareAndSet(this, null, ed)) { + upstream.subscribe(this); + return ed; + } + throw new IllegalStateException("A Sinks.ManyWithUpstream must be subscribed to a source only once"); + } + + @Override + public void subscribe(CoreSubscriber actual) { + Objects.requireNonNull(actual, "subscribe"); + EmitterInner inner = new EmitterInner<>(actual, this); + actual.onSubscribe(inner); + + if (inner.isCancelled()) { + return; + } + + if (add(inner)) { + if (inner.isCancelled()) { + remove(inner); + } + drain(); + } + else { + Throwable e = error; + if (e != null) { + inner.actual.onError(e); + } + else { + inner.actual.onComplete(); + } + } + } + + @Override + public void onComplete() { + //no particular error condition handling for onComplete + @SuppressWarnings("unused") EmitResult emitResult = tryEmitComplete(); + } + + @Override + public EmitResult tryEmitComplete() { + if (done) { + return EmitResult.FAIL_TERMINATED; + } + done = true; + drain(); + return EmitResult.OK; + } + + @Override + public void onError(Throwable throwable) { + emitError(throwable, Sinks.EmitFailureHandler.FAIL_FAST); + } + + @Override + public EmitResult tryEmitError(Throwable t) { + Objects.requireNonNull(t, "tryEmitError must be invoked with a non-null Throwable"); + if (done) { + return EmitResult.FAIL_TERMINATED; + } + if (Exceptions.addThrowable(ERROR, this, t)) { + done = true; + drain(); + return EmitResult.OK; + } + else { + return EmitResult.FAIL_TERMINATED; + } + } + + @Override + public void onNext(T t) { + if (sourceMode == Fuseable.ASYNC) { + drain(); + return; + } + emitNext(t, Sinks.EmitFailureHandler.FAIL_FAST); + } + + @Override + public EmitResult tryEmitNext(T t) { + if (done) { + return EmitResult.FAIL_TERMINATED; + } + + Objects.requireNonNull(t, "tryEmitNext must be invoked with a non-null value"); + + Queue q = queue; + + if (q == null) { + if (Operators.setOnce(S, this, Operators.emptySubscription())) { + q = Queues.get(prefetch).get(); + queue = q; + } + else { + for (; ; ) { + if (isCancelled()) { + return EmitResult.FAIL_CANCELLED; + } + q = queue; + if (q != null) { + break; + } + } + } + } + + if (!q.offer(t)) { + return subscribers == EMPTY ? EmitResult.FAIL_ZERO_SUBSCRIBER : EmitResult.FAIL_OVERFLOW; + } + drain(); + return EmitResult.OK; + } + + @Override + public int currentSubscriberCount() { + return subscribers.length; + } + + @Override + public Flux asFlux() { + return this; + } + + /** + * Return the number of parked elements in the emitter backlog. + * + * @return the number of parked elements in the emitter backlog. + */ + int getPending() { + Queue q = queue; + return q != null ? q.size() : 0; + } + + //TODO evaluate the use case for Disposable in the context of Sinks + @Override + public void dispose() { + onError(new CancellationException("Disposed")); + } + + @Override + public boolean isDisposed() { + return isTerminated() || isCancelled(); + } + + @Override + public void onSubscribe(final Subscription s) { + //since the CoreSubscriber nature isn't exposed to the user, the only path to onSubscribe is + //already guarded by UPSTREAM_DISPOSABLE. just in case the publisher misbehaves we still use setOnce + if (Operators.setOnce(S, this, s)) { + if (s instanceof Fuseable.QueueSubscription) { + @SuppressWarnings("unchecked") Fuseable.QueueSubscription f = + (Fuseable.QueueSubscription) s; + + int m = f.requestFusion(Fuseable.ANY); + if (m == Fuseable.SYNC) { + sourceMode = m; + queue = f; + drain(); + return; + } + else if (m == Fuseable.ASYNC) { + sourceMode = m; + queue = f; + s.request(Operators.unboundedOrPrefetch(prefetch)); + return; + } + } + + queue = Queues.get(prefetch).get(); + + s.request(Operators.unboundedOrPrefetch(prefetch)); + } + } + + /** + * Current error if any, default to null + * + * @return Current error if any, default to null + */ + @Nullable + Throwable getError() { + return error; + } + + /** + * @return true if all subscribers have actually been cancelled and the processor auto shut down + */ + boolean isCancelled() { + return Operators.cancelledSubscription() == s; + } + + /** + * Has this upstream finished or "completed" / "failed" ? + * + * @return has this upstream finished or "completed" / "failed" ? + */ + public boolean isTerminated() { + return done && getPending() == 0; + } + + @Override + public int getPrefetch() { + return prefetch; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.BUFFERED) return getPending(); + if (key == Attr.CANCELLED) return isCancelled(); + if (key == Attr.PREFETCH) return getPrefetch(); + + if (key == Attr.TERMINATED) return isTerminated(); + if (key == Attr.ERROR) return getError(); + if (key == Attr.CAPACITY) return getPrefetch(); + + return null; + } + + final void drain() { + if (WIP.getAndIncrement(this) != 0) { + return; + } + + int missed = 1; + + for (; ; ) { + + boolean d = done; + + Queue q = queue; + + boolean empty = q == null || q.isEmpty(); + + if (checkTerminated(d, empty)) { + return; + } + + FluxPublish.PubSubInner[] a = subscribers; + + if (a != EMPTY && !empty) { + long maxRequested = Long.MAX_VALUE; + + int len = a.length; + int cancel = 0; + + for (FluxPublish.PubSubInner inner : a) { + long r = inner.requested; + if (r >= 0L) { + maxRequested = Math.min(maxRequested, r); + } + else { //Long.MIN == PublishInner.CANCEL_REQUEST + cancel++; + } + } + + if (len == cancel) { + T v; + + try { + v = q.poll(); + } + catch (Throwable ex) { + Exceptions.addThrowable(ERROR, + this, Operators.onOperatorError(s, ex, currentContext())); + d = true; + v = null; + } + if (checkTerminated(d, v == null)) { + return; + } + if (sourceMode != Fuseable.SYNC) { + s.request(1); + } + continue; + } + + int e = 0; + + while (e < maxRequested && cancel != Integer.MIN_VALUE) { + d = done; + T v; + + try { + v = q.poll(); + } + catch (Throwable ex) { + Exceptions.addThrowable(ERROR, + this, Operators.onOperatorError(s, ex, currentContext())); + d = true; + v = null; + } + + empty = v == null; + + if (checkTerminated(d, empty)) { + return; + } + + if (empty) { + //async mode only needs to break but SYNC mode needs to perform terminal cleanup here... + if (sourceMode == Fuseable.SYNC) { + //the q is empty + done = true; + checkTerminated(true, true); + } + break; + } + + for (FluxPublish.PubSubInner inner : a) { + inner.actual.onNext(v); + if (Operators.producedCancellable(FluxPublish + .PublishInner.REQUESTED, inner, + 1) == Long.MIN_VALUE) { + cancel = Integer.MIN_VALUE; + } + } + + e++; + } + + if (e != 0 && sourceMode != Fuseable.SYNC) { + s.request(e); + } + + if (maxRequested != 0L && !empty) { + continue; + } + } + else if ( sourceMode == Fuseable.SYNC ) { + done = true; + if (checkTerminated(true, empty)) { //empty can be true if no subscriber + break; + } + } + + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + } + + @SuppressWarnings("unchecked") + FluxPublish.PubSubInner[] terminate() { + return SUBSCRIBERS.getAndSet(this, TERMINATED); + } + + boolean checkTerminated(boolean d, boolean empty) { + if (s == Operators.cancelledSubscription()) { + if (autoCancel) { + terminate(); + Queue q = queue; + if (q != null) { + q.clear(); + } + } + return true; + } + if (d) { + Throwable e = error; + if (e != null && e != Exceptions.TERMINATED) { + Queue q = queue; + if (q != null) { + q.clear(); + } + for (FluxPublish.PubSubInner inner : terminate()) { + inner.actual.onError(e); + } + return true; + } + else if (empty) { + for (FluxPublish.PubSubInner inner : terminate()) { + inner.actual.onComplete(); + } + return true; + } + } + return false; + } + + final boolean add(EmitterInner inner) { + for (; ; ) { + FluxPublish.PubSubInner[] a = subscribers; + if (a == TERMINATED) { + return false; + } + int n = a.length; + FluxPublish.PubSubInner[] b = new FluxPublish.PubSubInner[n + 1]; + System.arraycopy(a, 0, b, 0, n); + b[n] = inner; + if (SUBSCRIBERS.compareAndSet(this, a, b)) { + return true; + } + } + } + + final void remove(FluxPublish.PubSubInner inner) { + for (; ; ) { + FluxPublish.PubSubInner[] a = subscribers; + if (a == TERMINATED || a == EMPTY) { + return; + } + int n = a.length; + int j = -1; + for (int i = 0; i < n; i++) { + if (a[i] == inner) { + j = i; + break; + } + } + + if (j < 0) { + return; + } + + FluxPublish.PubSubInner[] b; + if (n == 1) { + b = EMPTY; + } + else { + b = new FluxPublish.PubSubInner[n - 1]; + System.arraycopy(a, 0, b, 0, j); + System.arraycopy(a, j + 1, b, j, n - j - 1); + } + if (SUBSCRIBERS.compareAndSet(this, a, b)) { + //contrary to FluxPublish, there is a possibility of auto-cancel, which + //happens when the removed inner makes the subscribers array EMPTY + if (autoCancel && b == EMPTY && Operators.terminate(S, this)) { + if (WIP.getAndIncrement(this) != 0) { + return; + } + terminate(); + Queue q = queue; + if (q != null) { + q.clear(); + } + } + return; + } + } + } + + static final class EmitterInner extends FluxPublish.PubSubInner { + + final SinkManyEmitterProcessor parent; + + EmitterInner(CoreSubscriber actual, SinkManyEmitterProcessor parent) { + super(actual); + this.parent = parent; + } + + @Override + void drainParent() { + parent.drain(); + } + + @Override + void removeAndDrainParent() { + parent.remove(this); + parent.drain(); + } + } + + static final class EmitterDisposable implements Disposable { + + @Nullable + SinkManyEmitterProcessor target; + + public EmitterDisposable(SinkManyEmitterProcessor emitterProcessor) { + this.target = emitterProcessor; + } + + @Override + public boolean isDisposed() { + return target == null || target.isDetached(); + } + + @Override + public void dispose() { + SinkManyEmitterProcessor t = target; + if (t == null) { + return; + } + if (t.detach() || t.isDetached()) { + target = null; + } + } + } + + +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/SinkManyReplayProcessor.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/SinkManyReplayProcessor.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/SinkManyReplayProcessor.java (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -0,0 +1,647 @@ +/* + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reactor.core.publisher; + +import java.time.Duration; +import java.util.Objects; +import java.util.concurrent.CancellationException; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.stream.Stream; + +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +import reactor.core.CoreSubscriber; +import reactor.core.Disposable; +import reactor.core.Fuseable; +import reactor.core.Scannable; +import reactor.core.publisher.Sinks.EmitResult; +import reactor.core.scheduler.Scheduler; +import reactor.core.scheduler.Schedulers; +import reactor.util.annotation.Nullable; +import reactor.util.concurrent.Queues; +import reactor.util.context.Context; + +import static reactor.core.publisher.FluxReplay.ReplaySubscriber.EMPTY; +import static reactor.core.publisher.FluxReplay.ReplaySubscriber.TERMINATED; + +/** + * Replays all or the last N items to Subscribers. + *

      + * + *

      + * + * @param the value type + */ +final class SinkManyReplayProcessor extends Flux implements InternalManySink, CoreSubscriber, ContextHolder, Disposable, Fuseable, Scannable { + + /** + * Create a {@link SinkManyReplayProcessor} that caches the last element it has pushed, + * replaying it to late subscribers. This is a buffer-based SinkManyReplayProcessor with + * a history size of 1. + *

      + * + * + * @param the type of the pushed elements + * + * @return a new {@link SinkManyReplayProcessor} that replays its last pushed element to each new + * {@link Subscriber} + */ + static SinkManyReplayProcessor cacheLast() { + return cacheLastOrDefault(null); + } + + /** + * Create a {@link SinkManyReplayProcessor} that caches the last element it has pushed, + * replaying it to late subscribers. If a {@link Subscriber} comes in before + * any value has been pushed, then the {@code defaultValue} is emitted instead. + * This is a buffer-based SinkManyReplayProcessor with a history size of 1. + *

      + * + * + * @param value a default value to start the sequence with in case nothing has been + * cached yet. + * @param the type of the pushed elements + * + * @return a new {@link SinkManyReplayProcessor} that replays its last pushed element to each new + * {@link Subscriber}, or a default one if nothing was pushed yet + */ + static SinkManyReplayProcessor cacheLastOrDefault(@Nullable T value) { + SinkManyReplayProcessor b = create(1); + if (value != null) { + b.onNext(value); + } + return b; + } + + /** + * Create a new {@link SinkManyReplayProcessor} that replays an unbounded number of elements, + * using a default internal {@link Queues#SMALL_BUFFER_SIZE Queue}. + * + * @param the type of the pushed elements + * + * @return a new {@link SinkManyReplayProcessor} that replays the whole history to each new + * {@link Subscriber}. + */ + static SinkManyReplayProcessor create() { + return create(Queues.SMALL_BUFFER_SIZE, true); + } + + /** + * Create a new {@link SinkManyReplayProcessor} that replays up to {@code historySize} + * elements. + * + * @param historySize the backlog size, ie. maximum items retained for replay. + * @param the type of the pushed elements + * + * @return a new {@link SinkManyReplayProcessor} that replays a limited history to each new + * {@link Subscriber}. + */ + static SinkManyReplayProcessor create(int historySize) { + return create(historySize, false); + } + + /** + * Create a new {@link SinkManyReplayProcessor} that either replay all the elements or a + * limited amount of elements depending on the {@code unbounded} parameter. + * + * @param historySize maximum items retained if bounded, or initial link size if unbounded + * @param unbounded true if "unlimited" data store must be supplied + * @param the type of the pushed elements + * + * @return a new {@link SinkManyReplayProcessor} that replays the whole history to each new + * {@link Subscriber} if configured as unbounded, a limited history otherwise. + */ + static SinkManyReplayProcessor create(int historySize, boolean unbounded) { + FluxReplay.ReplayBuffer buffer; + if (unbounded) { + buffer = new FluxReplay.UnboundedReplayBuffer<>(historySize); + } + else { + buffer = new FluxReplay.SizeBoundReplayBuffer<>(historySize); + } + return new SinkManyReplayProcessor<>(buffer); + } + + /** + * Creates a time-bounded replay processor. + *

      + * In this setting, the {@code SinkManyReplayProcessor} internally tags each observed item + * with a timestamp value supplied by the {@link Schedulers#parallel()} and keeps only + * those whose age is less than the supplied time value converted to milliseconds. For + * example, an item arrives at T=0 and the max age is set to 5; at T>=5 this first + * item is then evicted by any subsequent item or termination signal, leaving the + * buffer empty. + *

      + * Once the processor is terminated, subscribers subscribing to it will receive items + * that remained in the buffer after the terminal signal, regardless of their age. + *

      + * If an subscriber subscribes while the {@code SinkManyReplayProcessor} is active, it will + * observe only those items from within the buffer that have an age less than the + * specified time, and each item observed thereafter, even if the buffer evicts items + * due to the time constraint in the mean time. In other words, once an subscriber + * subscribes, it observes items without gaps in the sequence except for any outdated + * items at the beginning of the sequence. + *

      + * + * @param the type of items observed and emitted by the sink + * @param maxAge the maximum age of the contained items + * + * @return a new {@link SinkManyReplayProcessor} that replays elements based on their age. + */ + static SinkManyReplayProcessor createTimeout(Duration maxAge) { + return createTimeout(maxAge, Schedulers.parallel()); + } + + /** + * Creates a time-bounded replay processor. + *

      + * In this setting, the {@code SinkManyReplayProcessor} internally tags each observed item + * with a timestamp value supplied by the {@link Scheduler} and keeps only + * those whose age is less than the supplied time value converted to milliseconds. For + * example, an item arrives at T=0 and the max age is set to 5; at T>=5 this first + * item is then evicted by any subsequent item or termination signal, leaving the + * buffer empty. + *

      + * Once the processor is terminated, subscribers subscribing to it will receive items + * that remained in the buffer after the terminal signal, regardless of their age. + *

      + * If an subscriber subscribes while the {@code SinkManyReplayProcessor} is active, it will + * observe only those items from within the buffer that have an age less than the + * specified time, and each item observed thereafter, even if the buffer evicts items + * due to the time constraint in the mean time. In other words, once an subscriber + * subscribes, it observes items without gaps in the sequence except for any outdated + * items at the beginning of the sequence. + *

      + * + * @param the type of items observed and emitted by the sink + * @param maxAge the maximum age of the contained items + * + * @return a new {@link SinkManyReplayProcessor} that replays elements based on their age. + */ + static SinkManyReplayProcessor createTimeout(Duration maxAge, Scheduler scheduler) { + return createSizeAndTimeout(Integer.MAX_VALUE, maxAge, scheduler); + } + + /** + * Creates a time- and size-bounded replay processor. + *

      + * In this setting, the {@code SinkManyReplayProcessor} internally tags each received item + * with a timestamp value supplied by the {@link Schedulers#parallel()} and holds at + * most + * {@code size} items in its internal buffer. It evicts items from the start of the + * buffer if their age becomes less-than or equal to the supplied age in milliseconds + * or the buffer reaches its {@code size} limit. + *

      + * When subscribers subscribe to a terminated {@code SinkManyReplayProcessor}, they observe + * the items that remained in the buffer after the terminal signal, regardless of + * their age, but at most {@code size} items. + *

      + * If an subscriber subscribes while the {@code SinkManyReplayProcessor} is active, it will + * observe only those items from within the buffer that have age less than the + * specified time and each subsequent item, even if the buffer evicts items due to the + * time constraint in the mean time. In other words, once an subscriber subscribes, it + * observes items without gaps in the sequence except for the outdated items at the + * beginning of the sequence. + *

      + * + * @param the type of items observed and emitted by the sink + * @param maxAge the maximum age of the contained items + * @param size the maximum number of buffered items + * + * @return a new {@link SinkManyReplayProcessor} that replay up to {@code size} elements, but + * will evict them from its history based on their age. + */ + static SinkManyReplayProcessor createSizeAndTimeout(int size, Duration maxAge) { + return createSizeAndTimeout(size, maxAge, Schedulers.parallel()); + } + + /** + * Creates a time- and size-bounded replay processor. + *

      + * In this setting, the {@code SinkManyReplayProcessor} internally tags each received item + * with a timestamp value supplied by the {@link Scheduler} and holds at most + * {@code size} items in its internal buffer. It evicts items from the start of the + * buffer if their age becomes less-than or equal to the supplied age in milliseconds + * or the buffer reaches its {@code size} limit. + *

      + * When subscribers subscribe to a terminated {@code SinkManyReplayProcessor}, they observe + * the items that remained in the buffer after the terminal signal, regardless of + * their age, but at most {@code size} items. + *

      + * If an subscriber subscribes while the {@code SinkManyReplayProcessor} is active, it will + * observe only those items from within the buffer that have age less than the + * specified time and each subsequent item, even if the buffer evicts items due to the + * time constraint in the mean time. In other words, once an subscriber subscribes, it + * observes items without gaps in the sequence except for the outdated items at the + * beginning of the sequence. + *

      + * + * @param the type of items observed and emitted by the sink + * @param maxAge the maximum age of the contained items in milliseconds + * @param size the maximum number of buffered items + * @param scheduler the {@link Scheduler} that provides the current time + * + * @return a new {@link SinkManyReplayProcessor} that replay up to {@code size} elements, but + * will evict them from its history based on their age. + */ + static SinkManyReplayProcessor createSizeAndTimeout(int size, + Duration maxAge, + Scheduler scheduler) { + Objects.requireNonNull(scheduler, "scheduler is null"); + if (size <= 0) { + throw new IllegalArgumentException("size > 0 required but it was " + size); + } + return new SinkManyReplayProcessor<>(new FluxReplay.SizeAndTimeBoundReplayBuffer<>(size, + maxAge.toNanos(), + scheduler)); + } + + final FluxReplay.ReplayBuffer buffer; + + Subscription subscription; + + volatile FluxReplay.ReplaySubscription[] subscribers; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater + SUBSCRIBERS = AtomicReferenceFieldUpdater.newUpdater(SinkManyReplayProcessor.class, + FluxReplay.ReplaySubscription[].class, + "subscribers"); + + SinkManyReplayProcessor(FluxReplay.ReplayBuffer buffer) { + this.buffer = buffer; + SUBSCRIBERS.lazySet(this, EMPTY); + } + + @Override + public void subscribe(CoreSubscriber actual) { + Objects.requireNonNull(actual, "subscribe"); + FluxReplay.ReplaySubscription rs = new ReplayInner<>(actual, this); + actual.onSubscribe(rs); + + if (add(rs)) { + if (rs.isCancelled()) { + remove(rs); + return; + } + } + buffer.replay(rs); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT){ + return subscription; + } + if (key == Attr.CAPACITY) return buffer.capacity(); + if (key == Attr.TERMINATED) return buffer.isDone(); + if (key == Attr.ERROR) return buffer.getError(); + + return null; + } + + @Override + public Stream inners() { + return Stream.of(subscribers); + } + + @Override + public void dispose() { + emitError(new CancellationException("Disposed"), Sinks.EmitFailureHandler.FAIL_FAST); + } + + @Override + public boolean isDisposed() { + return buffer.isDone(); + } + + boolean add(FluxReplay.ReplaySubscription rs) { + for (; ; ) { + FluxReplay.ReplaySubscription[] a = subscribers; + if (a == TERMINATED) { + return false; + } + int n = a.length; + + @SuppressWarnings("unchecked") FluxReplay.ReplaySubscription[] b = + new ReplayInner[n + 1]; + System.arraycopy(a, 0, b, 0, n); + b[n] = rs; + if (SUBSCRIBERS.compareAndSet(this, a, b)) { + return true; + } + } + } + + @SuppressWarnings("unchecked") + void remove(FluxReplay.ReplaySubscription rs) { + outer: + for (; ; ) { + FluxReplay.ReplaySubscription[] a = subscribers; + if (a == TERMINATED || a == EMPTY) { + return; + } + int n = a.length; + + for (int i = 0; i < n; i++) { + if (a[i] == rs) { + FluxReplay.ReplaySubscription[] b; + + if (n == 1) { + b = EMPTY; + } + else { + b = new ReplayInner[n - 1]; + System.arraycopy(a, 0, b, 0, i); + System.arraycopy(a, i + 1, b, i, n - i - 1); + } + + if (SUBSCRIBERS.compareAndSet(this, a, b)) { + return; + } + + continue outer; + } + } + + break; + } + } + + @Override + public void onSubscribe(Subscription s) { + if (buffer.isDone()) { + s.cancel(); + } + else if (Operators.validate(subscription, s)) { + subscription = s; + s.request(Long.MAX_VALUE); + } + } + + @Override + public Context currentContext() { + return Operators.multiSubscribersContext(subscribers); + } + + @Override + public int getPrefetch() { + return Integer.MAX_VALUE; + } + + @Override + public void onComplete() { + //no particular error condition handling for onComplete + @SuppressWarnings("unused") EmitResult emitResult = tryEmitComplete(); + } + + @Override + public EmitResult tryEmitComplete() { + FluxReplay.ReplayBuffer b = buffer; + if (b.isDone()) { + return EmitResult.FAIL_TERMINATED; + } + + b.onComplete(); + + @SuppressWarnings("unchecked") FluxReplay.ReplaySubscription[] a = + SUBSCRIBERS.getAndSet(this, TERMINATED); + + for (FluxReplay.ReplaySubscription rs : a) { + b.replay(rs); + } + return EmitResult.OK; + } + + @Override + public void onError(Throwable throwable) { + emitError(throwable, Sinks.EmitFailureHandler.FAIL_FAST); + } + + @Override + public EmitResult tryEmitError(Throwable t) { + FluxReplay.ReplayBuffer b = buffer; + if (b.isDone()) { + return EmitResult.FAIL_TERMINATED; + } + + b.onError(t); + + @SuppressWarnings("unchecked") FluxReplay.ReplaySubscription[] a = + SUBSCRIBERS.getAndSet(this, TERMINATED); + + for (FluxReplay.ReplaySubscription rs : a) { + b.replay(rs); + } + return EmitResult.OK; + } + + @Override + public void onNext(T t) { + emitNext(t, Sinks.EmitFailureHandler.FAIL_FAST); + } + + @Override + public EmitResult tryEmitNext(T t) { + FluxReplay.ReplayBuffer b = buffer; + if (b.isDone()) { + return EmitResult.FAIL_TERMINATED; + } + + //note: SinkManyReplayProcessor can so far ALWAYS buffer the element, no FAIL_ZERO_SUBSCRIBER here + b.add(t); + for (FluxReplay.ReplaySubscription rs : subscribers) { + b.replay(rs); + } + return EmitResult.OK; + } + + @Override + public int currentSubscriberCount() { + return subscribers.length; + } + + @Override + public Flux asFlux() { + return this; + } + + static final class ReplayInner + implements FluxReplay.ReplaySubscription { + + final CoreSubscriber actual; + + final SinkManyReplayProcessor parent; + + final FluxReplay.ReplayBuffer buffer; + + int index; + + int tailIndex; + + Object node; + + volatile int wip; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(ReplayInner.class, + "wip"); + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(ReplayInner.class, + "requested"); + + int fusionMode; + + ReplayInner(CoreSubscriber actual, + SinkManyReplayProcessor parent) { + this.actual = actual; + this.parent = parent; + this.buffer = parent.buffer; + } + + @Override + public long requested() { + return requested; + } + + @Override + public boolean isCancelled() { + return requested == Long.MIN_VALUE; + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public int requestFusion(int requestedMode) { + if ((requestedMode & ASYNC) != 0) { + fusionMode = ASYNC; + return ASYNC; + } + return NONE; + } + + @Override + @Nullable + public T poll() { + return buffer.poll(this); + } + + @Override + public void clear() { + buffer.clear(this); + } + + @Override + public boolean isEmpty() { + return buffer.isEmpty(this); + } + + @Override + public int size() { + return buffer.size(this); + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + if (fusionMode() == NONE) { + Operators.addCapCancellable(REQUESTED, this, n); + } + buffer.replay(this); + } + } + + @Override + public void requestMore(int index) { + this.index = index; + } + + @Override + public void cancel() { + if (REQUESTED.getAndSet(this, Long.MIN_VALUE) != Long.MIN_VALUE) { + parent.remove(this); + + if (enter()) { + node = null; + } + } + } + + @Override + public void node(@Nullable Object node) { + this.node = node; + } + + @Override + public int fusionMode() { + return fusionMode; + } + + @Override + @Nullable + public Object node() { + return node; + } + + @Override + public int index() { + return index; + } + + @Override + public void index(int index) { + this.index = index; + } + + @Override + public int tailIndex() { + return tailIndex; + } + + @Override + public void tailIndex(int tailIndex) { + this.tailIndex = tailIndex; + } + + @Override + public boolean enter() { + return WIP.getAndIncrement(this) == 0; + } + + @Override + public int leave(int missed) { + return WIP.addAndGet(this, -missed); + } + + @Override + public void produced(long n) { + REQUESTED.addAndGet(this, -n); + } + } +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/SinkManyUnicast.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/SinkManyUnicast.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/SinkManyUnicast.java (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -0,0 +1,514 @@ +/* + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reactor.core.publisher; + +import java.util.Objects; +import java.util.Queue; +import java.util.concurrent.CancellationException; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.stream.Stream; + +import reactor.core.CoreSubscriber; +import reactor.core.Disposable; +import reactor.core.Fuseable; +import reactor.core.Scannable; +import reactor.core.publisher.Sinks.EmitResult; +import reactor.util.annotation.Nullable; +import reactor.util.concurrent.Queues; +import reactor.util.context.Context; + +/** + * A {@link Sinks.Many} implementation that takes a custom queue and allows + * only a single subscriber. {@link SinkManyUnicast} allows multiplexing of the events which + * means that it supports multiple producers and only one consumer. + * However, it should be noticed that multi-producer case is only valid if appropriate + * Queue is provided. Otherwise, it could break + * Reactive Streams Spec if Publishers + * publish on different threads. + * + *

      + * + *

      + * + *
      + *
      + * + *

      + * Note: SinkManyUnicast does not respect the actual subscriber's + * demand as it is described in + * Reactive Streams Spec. However, + * SinkManyUnicast embraces configurable Queue internally which allows enabling + * backpressure support and preventing of consumer's overwhelming. + * + * Hence, interaction model between producers and SinkManyUnicast will be PUSH + * only. In opposite, interaction model between SinkManyUnicast and consumer will be + * PUSH-PULL as defined in + * Reactive Streams Spec. + * + * In the case when upstream's signals overflow the bound of internal Queue, + * SinkManyUnicast will fail with signaling onError( + * {@literal reactor.core.Exceptions.OverflowException}). + * + *

      + * + *

      + *

      + * + *
      + *
      + * + *

      + * Note: The implementation keeps the order of signals. That means that in + * case of terminal signal (completion or error signals) it will be postponed + * until all of the previous signals has been consumed. + *

      + * + *

      + *

      + * + * @param the input and output type + */ +final class SinkManyUnicast extends Flux implements InternalManySink, Disposable, Fuseable.QueueSubscription, Fuseable { + + /** + * Create a new {@link SinkManyUnicast} that will buffer on an internal queue in an + * unbounded fashion. + * + * @param the relayed type + * @return a unicast {@link Sinks.Many} + */ + static SinkManyUnicast create() { + return new SinkManyUnicast<>(Queues.unbounded().get()); + } + + /** + * Create a new {@link SinkManyUnicast} that will buffer on a provided queue in an + * unbounded fashion. + * + * @param queue the buffering queue + * @param the relayed type + * @return a unicast {@link Sinks.Many} + */ + static SinkManyUnicast create(Queue queue) { + return new SinkManyUnicast<>(Hooks.wrapQueue(queue)); + } + + /** + * Create a new {@link SinkManyUnicast} that will buffer on a provided queue in an + * unbounded fashion. + * + * @param queue the buffering queue + * @param endcallback called on any terminal signal + * @param the relayed type + * @return a unicast {@link Sinks.Many} + */ + static SinkManyUnicast create(Queue queue, Disposable endcallback) { + return new SinkManyUnicast<>(Hooks.wrapQueue(queue), endcallback); + } + + final Queue queue; + + volatile Disposable onTerminate; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater ON_TERMINATE = + AtomicReferenceFieldUpdater.newUpdater(SinkManyUnicast.class, Disposable.class, "onTerminate"); + + volatile boolean done; + Throwable error; + + boolean hasDownstream; //important to not loose the downstream too early and miss discard hook, while having relevant hasDownstreams() + volatile CoreSubscriber actual; + + volatile boolean cancelled; + + volatile int once; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater ONCE = + AtomicIntegerFieldUpdater.newUpdater(SinkManyUnicast.class, "once"); + + volatile int wip; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(SinkManyUnicast.class, "wip"); + + volatile int discardGuard; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater DISCARD_GUARD = + AtomicIntegerFieldUpdater.newUpdater(SinkManyUnicast.class, "discardGuard"); + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(SinkManyUnicast.class, "requested"); + + boolean outputFused; + + SinkManyUnicast(Queue queue) { + this.queue = Objects.requireNonNull(queue, "queue"); + this.onTerminate = null; + } + + SinkManyUnicast(Queue queue, Disposable onTerminate) { + this.queue = Objects.requireNonNull(queue, "queue"); + this.onTerminate = Objects.requireNonNull(onTerminate, "onTerminate"); + } + + @Override + public Stream inners() { + return hasDownstream ? Stream.of(Scannable.from(actual)) : Stream.empty(); + } + + @Override + public Object scanUnsafe(Attr key) { + if (Attr.ACTUAL == key) return actual; + if (Attr.BUFFERED == key) return queue.size(); + if (Attr.CAPACITY == key) return Queues.capacity(this.queue); + if (Attr.PREFETCH == key) return Integer.MAX_VALUE; + if (Attr.CANCELLED == key) return cancelled; + if (Attr.TERMINATED == key) return done; + if (Attr.ERROR == key) return error; + + return null; + } + + @Override + public EmitResult tryEmitComplete() { + if (done) { + return EmitResult.FAIL_TERMINATED; + } + if (cancelled) { + return EmitResult.FAIL_CANCELLED; + } + + done = true; + + doTerminate(); + + drain(null); + return EmitResult.OK; + } + + @Override + public EmitResult tryEmitError(Throwable t) { + if (done) { + return EmitResult.FAIL_TERMINATED; + } + if (cancelled) { + return EmitResult.FAIL_CANCELLED; + } + + error = t; + done = true; + + doTerminate(); + + drain(null); + return EmitResult.OK; + } + + @Override + public EmitResult tryEmitNext(T t) { + if (done) { + return EmitResult.FAIL_TERMINATED; + } + if (cancelled) { + return EmitResult.FAIL_CANCELLED; + } + + if (!queue.offer(t)) { + return (once > 0) ? EmitResult.FAIL_OVERFLOW : EmitResult.FAIL_ZERO_SUBSCRIBER; + } + drain(t); + return EmitResult.OK; + } + + @Override + public int currentSubscriberCount() { + return hasDownstream ? 1 : 0; + } + + @Override + public Flux asFlux() { + return this; + } + + void doTerminate() { + Disposable r = onTerminate; + if (r != null && ON_TERMINATE.compareAndSet(this, r, null)) { + r.dispose(); + } + } + + void drainRegular(CoreSubscriber a) { + int missed = 1; + + final Queue q = queue; + + for (;;) { + + long r = requested; + long e = 0L; + + while (r != e) { + boolean d = done; + + T t = q.poll(); + boolean empty = t == null; + + if (checkTerminated(d, empty, a, q, t)) { + return; + } + + if (empty) { + break; + } + + a.onNext(t); + + e++; + } + + if (r == e) { + if (checkTerminated(done, q.isEmpty(), a, q, null)) { + return; + } + } + + if (e != 0 && r != Long.MAX_VALUE) { + REQUESTED.addAndGet(this, -e); + } + + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + } + + void drainFused(CoreSubscriber a) { + int missed = 1; + + for (;;) { + + if (cancelled) { + // We are the holder of the queue, but we still have to perform discarding under the guarded block + // to prevent any racing done by downstream + this.clear(); + hasDownstream = false; + return; + } + + boolean d = done; + + a.onNext(null); + + if (d) { + hasDownstream = false; + + Throwable ex = error; + if (ex != null) { + a.onError(ex); + } else { + a.onComplete(); + } + return; + } + + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + } + + void drain(@Nullable T dataSignalOfferedBeforeDrain) { + if (WIP.getAndIncrement(this) != 0) { + if (dataSignalOfferedBeforeDrain != null) { + if (cancelled) { + Operators.onDiscard(dataSignalOfferedBeforeDrain, + actual.currentContext()); + } + else if (done) { + Operators.onNextDropped(dataSignalOfferedBeforeDrain, + currentContext()); + } + } + return; + } + + int missed = 1; + + for (;;) { + CoreSubscriber a = actual; + if (a != null) { + + if (outputFused) { + drainFused(a); + } else { + drainRegular(a); + } + return; + } + + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + } + + boolean checkTerminated(boolean d, boolean empty, CoreSubscriber a, Queue q, @Nullable T t) { + if (cancelled) { + Operators.onDiscard(t, a.currentContext()); + Operators.onDiscardQueueWithClear(q, a.currentContext(), null); + hasDownstream = false; + return true; + } + if (d && empty) { + Throwable e = error; + hasDownstream = false; + if (e != null) { + a.onError(e); + } else { + a.onComplete(); + } + return true; + } + + return false; + } + + @Override + public int getPrefetch() { + return Integer.MAX_VALUE; + } + + @Override + public Context currentContext() { + CoreSubscriber actual = this.actual; + return actual != null ? actual.currentContext() : Context.empty(); + } + + @Override + public void subscribe(CoreSubscriber actual) { + Objects.requireNonNull(actual, "subscribe"); + if (once == 0 && ONCE.compareAndSet(this, 0, 1)) { + + this.hasDownstream = true; + actual.onSubscribe(this); + this.actual = actual; + if (cancelled) { + this.hasDownstream = false; + } else { + drain(null); + } + } else { + Operators.error(actual, new IllegalStateException("Sinks.many().unicast() sinks only allow a single Subscriber")); + } + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + Operators.addCap(REQUESTED, this, n); + drain(null); + } + } + + @Override + public void cancel() { + if (cancelled) { + return; + } + cancelled = true; + + doTerminate(); + + if (WIP.getAndIncrement(this) == 0) { + if (!outputFused) { + // discard MUST be happening only and only if there is no racing on elements consumption + // which is guaranteed by the WIP guard here in case non-fused output + Operators.onDiscardQueueWithClear(queue, currentContext(), null); + } + hasDownstream = false; + } + } + + @Override + @Nullable + public T poll() { + return queue.poll(); + } + + @Override + public int size() { + return queue.size(); + } + + @Override + public boolean isEmpty() { + return queue.isEmpty(); + } + + @Override + public void clear() { + // use guard on the queue instance as the best way to ensure there is no racing on draining + // the call to this method must be done only during the ASYNC fusion so all the callers will be waiting + // this should not be performance costly with the assumption the cancel is rare operation + if (DISCARD_GUARD.getAndIncrement(this) != 0) { + return; + } + + int missed = 1; + + for (;;) { + Operators.onDiscardQueueWithClear(queue, currentContext(), null); + + int dg = discardGuard; + if (missed == dg) { + missed = DISCARD_GUARD.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + else { + missed = dg; + } + } + } + + @Override + public int requestFusion(int requestedMode) { + if ((requestedMode & Fuseable.ASYNC) != 0) { + outputFused = true; + return Fuseable.ASYNC; + } + return Fuseable.NONE; + } + + @Override + public void dispose() { + emitError(new CancellationException("Disposed"), Sinks.EmitFailureHandler.FAIL_FAST); + } + + @Override + public boolean isDisposed() { + return cancelled || done; + } +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/SinkManyUnicastNoBackpressure.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/SinkManyUnicastNoBackpressure.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/SinkManyUnicastNoBackpressure.java (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2020-2022 VMware Inc. or its affiliates, All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reactor.core.publisher; + +import java.util.Objects; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.stream.Stream; + +import org.reactivestreams.Subscription; + +import reactor.core.CoreSubscriber; +import reactor.core.Scannable; +import reactor.core.publisher.Sinks.EmitResult; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +final class SinkManyUnicastNoBackpressure extends Flux implements InternalManySink, Subscription, ContextHolder { + + public static SinkManyUnicastNoBackpressure create() { + return new SinkManyUnicastNoBackpressure<>(); + } + + enum State { + INITIAL, + SUBSCRIBED, + TERMINATED, + CANCELLED, + } + + volatile State state; + + @SuppressWarnings("rawtypes") + private static final AtomicReferenceFieldUpdater STATE = AtomicReferenceFieldUpdater.newUpdater( + SinkManyUnicastNoBackpressure.class, + State.class, + "state" + ); + + private volatile CoreSubscriber actual = null; + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(SinkManyUnicastNoBackpressure.class, "requested"); + + SinkManyUnicastNoBackpressure() { + STATE.lazySet(this, State.INITIAL); + } + + @Override + public int currentSubscriberCount() { + return state == State.SUBSCRIBED ? 1 : 0; + } + + @Override + public Flux asFlux() { + return this; + } + + @Override + public void subscribe(CoreSubscriber actual) { + Objects.requireNonNull(actual, "subscribe"); + + if (!STATE.compareAndSet(this, State.INITIAL, State.SUBSCRIBED)) { + Operators.reportThrowInSubscribe(actual, new IllegalStateException("Unicast Sinks.Many allows only a single Subscriber")); + return; + } + + this.actual = actual; + actual.onSubscribe(this); + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + Operators.addCap(REQUESTED, this, n); + } + } + + @Override + public void cancel() { + if (STATE.getAndSet(this, State.CANCELLED) == State.SUBSCRIBED) { + actual = null; + } + } + + @Override + public Context currentContext() { + CoreSubscriber actual = this.actual; + return actual != null ? actual.currentContext() : Context.empty(); + } + + @Override + public EmitResult tryEmitNext(T t) { + Objects.requireNonNull(t, "t"); + + switch (state) { + case INITIAL: + return EmitResult.FAIL_ZERO_SUBSCRIBER; + case SUBSCRIBED: + if (requested == 0L) { + return EmitResult.FAIL_OVERFLOW; + } + + actual.onNext(t); + Operators.produced(REQUESTED, this, 1); + return EmitResult.OK; + case TERMINATED: + return EmitResult.FAIL_TERMINATED; + case CANCELLED: + return EmitResult.FAIL_CANCELLED; + default: + throw new IllegalStateException(); + } + } + + @Override + public EmitResult tryEmitError(Throwable t) { + Objects.requireNonNull(t, "t"); + for(;;) { //for the benefit of retrying SUBSCRIBED + State s = this.state; + switch (s) { + case INITIAL: + return EmitResult.FAIL_ZERO_SUBSCRIBER; + case SUBSCRIBED: + if (STATE.compareAndSet(this, s, State.TERMINATED)) { + actual.onError(t); + actual = null; + return EmitResult.OK; + } + continue; + case TERMINATED: + return EmitResult.FAIL_TERMINATED; + case CANCELLED: + return EmitResult.FAIL_CANCELLED; + default: + throw new IllegalStateException(); + } + } + } + + @Override + public EmitResult tryEmitComplete() { + for (;;) { //for the benefit of retrying SUBSCRIBED + State s = this.state; + switch (s) { + case INITIAL: + return EmitResult.FAIL_ZERO_SUBSCRIBER; + case SUBSCRIBED: + if (STATE.compareAndSet(this, s, State.TERMINATED)) { + actual.onComplete(); + actual = null; + return EmitResult.OK; + } + continue; + case TERMINATED: + return EmitResult.FAIL_TERMINATED; + case CANCELLED: + return EmitResult.FAIL_CANCELLED; + default: + throw new IllegalStateException(); + } + } + } + + @Override + public Stream inners() { + CoreSubscriber a = actual; + return a == null ? Stream.empty() : Stream.of(Scannable.from(a)); + } + + @Nullable + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.ACTUAL) return actual; + if (key == Attr.TERMINATED) return state == State.TERMINATED; + if (key == Attr.CANCELLED) return state == State.CANCELLED; + + return null; + } +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/SinkOneMulticast.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/SinkOneMulticast.java (.../SinkOneMulticast.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/SinkOneMulticast.java (.../SinkOneMulticast.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,58 +25,55 @@ final class SinkOneMulticast extends SinkEmptyMulticast implements InternalOneSink { + @SuppressWarnings("rawtypes") + static final Inner[] TERMINATED_VALUE = new Inner[0]; + + static final int STATE_VALUE = 1; + @Nullable O value; @Override - public EmitResult tryEmitEmpty() { - return tryEmitValue(null); + boolean isTerminated(Inner[] array) { + return array == TERMINATED_VALUE || super.isTerminated(array); } @Override - @SuppressWarnings("unchecked") - public EmitResult tryEmitError(Throwable cause) { - Objects.requireNonNull(cause, "onError cannot be null"); + public EmitResult tryEmitValue(@Nullable O value) { + Inner[] prevSubscribers = this.subscribers; - Inner[] prevSubscribers = SUBSCRIBERS.getAndSet(this, TERMINATED); - if (prevSubscribers == TERMINATED) { + if (isTerminated(prevSubscribers)) { return EmitResult.FAIL_TERMINATED; } - error = cause; - value = null; - - for (Inner as : prevSubscribers) { - as.error(cause); + if (value == null) { + return tryEmitEmpty(); } - return EmitResult.OK; - } - @Override - public EmitResult tryEmitValue(@Nullable O value) { - @SuppressWarnings("unchecked") Inner[] array = SUBSCRIBERS.getAndSet(this, TERMINATED); - if (array == TERMINATED) { - return EmitResult.FAIL_TERMINATED; - } - this.value = value; - if (value == null) { - for (Inner as : array) { - as.complete(); + + for (;;) { + if (SUBSCRIBERS.compareAndSet(this, prevSubscribers, TERMINATED_VALUE)) { + break; } - } - else { - for (Inner as : array) { - as.complete(value); + + prevSubscribers = this.subscribers; + if (isTerminated(prevSubscribers)) { + return EmitResult.FAIL_TERMINATED; } } + + for (Inner as : prevSubscribers) { + as.complete(value); + } + return EmitResult.OK; } @Override public Object scanUnsafe(Attr key) { - if (key == Attr.TERMINATED) return subscribers == TERMINATED; - if (key == Attr.ERROR) return error; + if (key == Attr.TERMINATED) return isTerminated(subscribers); + if (key == Attr.ERROR) return subscribers == TERMINATED_ERROR ? error : null; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; return null; @@ -86,25 +83,48 @@ public void subscribe(final CoreSubscriber actual) { NextInner as = new NextInner<>(actual, this); actual.onSubscribe(as); - if (add(as)) { + final int addState = add(as); + if (addState == STATE_ADDED) { if (as.isCancelled()) { remove(as); } } + else if (addState == STATE_ERROR) { + actual.onError(error); + } + else if (addState == STATE_EMPTY) { + as.complete(); + } else { - Throwable ex = error; - if (ex != null) { - actual.onError(ex); + as.complete(value); + } + } + + @Override + int add(Inner ps) { + for (; ; ) { + Inner[] a = subscribers; + + if (a == TERMINATED_EMPTY) { + return STATE_EMPTY; } - else { - O v = value; - if (v != null) { - as.complete(v); - } - else { - as.complete(); - } + + if (a == TERMINATED_ERROR) { + return STATE_ERROR; } + + if (a == TERMINATED_VALUE) { + return STATE_VALUE; + } + + int n = a.length; + @SuppressWarnings("unchecked") Inner[] b = new Inner[n + 1]; + System.arraycopy(a, 0, b, 0, n); + b[n] = ps; + + if (SUBSCRIBERS.compareAndSet(this, a, b)) { + return STATE_ADDED; + } } } @@ -127,6 +147,11 @@ } @Override + protected void doOnCancel() { + parent.remove(this); + } + + @Override public void error(Throwable t) { if (!isCancelled()) { actual().onError(t); Index: 3rdParty_sources/reactor/reactor/core/publisher/Sinks.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/Sinks.java (.../Sinks.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/Sinks.java (.../Sinks.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2020-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import java.time.Duration; import java.util.Queue; +import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; import reactor.core.Disposable; @@ -34,7 +35,7 @@ * semantics. These standalone sinks expose {@link Many#tryEmitNext(Object) tryEmit} methods that return an {@link EmitResult} enum, * allowing to atomically fail in case the attempted signal is inconsistent with the spec and/or the state of the sink. *

      - * This class exposes a collection of ({@link Sinks.Many} builders and {@link Sinks.One} factories. Unless constructed through the + * This class exposes a collection of ({@link Many} builders and {@link One} factories. Unless constructed through the * {@link #unsafe()} spec, these sinks are thread safe in the sense that they will detect concurrent access and fail fast on one of * the attempts. {@link #unsafe()} sinks on the other hand are expected to be externally synchronized (typically by being called from * within a Reactive Streams-compliant context, like a {@link Subscriber} or an operator, which means it is ok to remove the overhead @@ -49,52 +50,52 @@ } /** - * A {@link Sinks.Empty} which exclusively produces one terminal signal: error or complete. + * A {@link Empty} which exclusively produces one terminal signal: error or complete. * It has the following characteristics: *

        *
      • Multicast
      • *
      • Backpressure : this sink does not need any demand since it can only signal error or completion
      • *
      • Replaying: Replay the terminal signal (error or complete).
      • *
      - * Use {@link Sinks.Empty#asMono()} to expose the {@link Mono} view of the sink to downstream consumers. + * Use {@link Empty#asMono()} to expose the {@link Mono} view of the sink to downstream consumers. * - * @return a new {@link Sinks.Empty} + * @return a new {@link Empty} * @see RootSpec#empty() */ - public static Sinks.Empty empty() { - return SinksSpecs.DEFAULT_ROOT_SPEC.empty(); + public static Empty empty() { + return SinksSpecs.DEFAULT_SINKS.empty(); } /** - * A {@link Sinks.One} that works like a conceptual promise: it can be completed + * A {@link One} that works like a conceptual promise: it can be completed * with or without a value at any time, but only once. This completion is replayed to late subscribers. - * Calling {@link One#tryEmitValue(Object)} (or {@link One#emitValue(Object, Sinks.EmitFailureHandler)}) is enough and will + * Calling {@link One#tryEmitValue(Object)} (or {@link One#emitValue(Object, EmitFailureHandler)}) is enough and will * implicitly produce a {@link Subscriber#onComplete()} signal as well. *

      * Use {@link One#asMono()} to expose the {@link Mono} view of the sink to downstream consumers. * - * @return a new {@link Sinks.One} + * @return a new {@link One} * @see RootSpec#one() */ - public static Sinks.One one() { - return SinksSpecs.DEFAULT_ROOT_SPEC.one(); + public static One one() { + return SinksSpecs.DEFAULT_SINKS.one(); } /** - * Help building {@link Sinks.Many} sinks that will broadcast multiple signals to one or more {@link Subscriber}. + * Help building {@link Many} sinks that will broadcast multiple signals to one or more {@link Subscriber}. *

      * Use {@link Many#asFlux()} to expose the {@link Flux} view of the sink to the downstream consumers. * * @return {@link ManySpec} * @see RootSpec#many() */ public static ManySpec many() { - return SinksSpecs.DEFAULT_ROOT_SPEC.many(); + return SinksSpecs.DEFAULT_SINKS.many(); } /** * Return a {@link RootSpec root spec} for more advanced use cases such as building operators. - * Unsafe {@link Sinks.Many}, {@link Sinks.One} and {@link Sinks.Empty} are not serialized nor thread safe, + * Unsafe {@link Many}, {@link One} and {@link Empty} are not serialized nor thread safe, * which implies they MUST be externally synchronized so as to respect the Reactive Streams specification. * This can typically be the case when the sinks are being called from within a Reactive Streams-compliant context, * like a {@link Subscriber} or an operator. In turn, this allows the sinks to have less overhead, since they @@ -107,7 +108,7 @@ } /** - * Represents the immediate result of an emit attempt (eg. in {@link Sinks.Many#tryEmitNext(Object)}. + * Represents the immediate result of an emit attempt (eg. in {@link Many#tryEmitNext(Object)}. * This does not guarantee that a signal is consumed, it simply refers to the sink state when an emit method is invoked. * This is a particularly important distinction with regard to {@link #FAIL_CANCELLED} which means the sink is -now- * interrupted and emission can't proceed. Consequently, it is possible to emit a signal and obtain an "OK" status even @@ -223,7 +224,25 @@ } /** - * A handler supporting the emit API (eg. {@link Many#emitNext(Object, Sinks.EmitFailureHandler)}), + * + * @author Animesh Chaturvedi + */ + static class OptimisticEmitFailureHandler implements EmitFailureHandler { + + private final long deadline; + + OptimisticEmitFailureHandler(Duration duration){ + this.deadline = System.nanoTime() + duration.toNanos(); + } + + @Override + public boolean onEmitFailure(SignalType signalType, EmitResult emitResult) { + return emitResult.equals(EmitResult.FAIL_NON_SERIALIZED) && (System.nanoTime() < this.deadline); + } + } + + /** + * A handler supporting the emit API (eg. {@link Many#emitNext(Object, EmitFailureHandler)}), * checking non-successful emission results from underlying {@link Many#tryEmitNext(Object) tryEmit} * API calls to decide whether or not such calls should be retried. * Other than instructing to retry, the handlers are allowed to have side effects @@ -243,6 +262,24 @@ EmitFailureHandler FAIL_FAST = (signalType, emission) -> false; /** + * Create an {@link EmitFailureHandler} which will busy loop in case of concurrent use + * of the sink ({@link EmitResult#FAIL_NON_SERIALIZED}, up to a deadline. + * The deadline is computed immediately from the current time (construction time) + * + provided {@link Duration}. + *

      + * As a result there will always be some delay between this computation and the actual first + * use of the handler (at a minimum, the time it takes for the first sink emission attempt). + * Consider this when choosing the {@link Duration}, and probably prefer something above 100ms, + * and don't cache the returning handler for later usage. + * + * @param duration {@link Duration} for the deadline + * @return an optimistic and bounded busy-looping {@link EmitFailureHandler} + */ + static EmitFailureHandler busyLooping(Duration duration){ + return new OptimisticEmitFailureHandler(duration); + } + + /** * Decide whether the emission should be retried, depending on the provided {@link EmitResult} * and the type of operation that was attempted (represented as a {@link SignalType}). * Side effects are allowed. @@ -254,65 +291,74 @@ boolean onEmitFailure(SignalType signalType, EmitResult emitResult); } + //implementation note: this should now only be implemented by the Sinks.unsafe() path /** - * Provides a choice of {@link Sinks.One}/{@link Sinks.Empty} factories and - * {@link Sinks.ManySpec further specs} for {@link Sinks.Many}. + * Provides a choice of {@link One}/{@link Empty} factories and + * {@link ManySpec further specs} for {@link Many}. */ public interface RootSpec { /** - * A {@link Sinks.Empty} which exclusively produces one terminal signal: error or complete. + * A {@link Empty} which exclusively produces one terminal signal: error or complete. * It has the following characteristics: *

        *
      • Multicast
      • *
      • Backpressure : this sink does not need any demand since it can only signal error or completion
      • *
      • Replaying: Replay the terminal signal (error or complete).
      • *
      - * Use {@link Sinks.Empty#asMono()} to expose the {@link Mono} view of the sink to downstream consumers. + * Use {@link Empty#asMono()} to expose the {@link Mono} view of the sink to downstream consumers. */ - Sinks.Empty empty(); + Empty empty(); /** - * A {@link Sinks.One} that works like a conceptual promise: it can be completed + * A {@link One} that works like a conceptual promise: it can be completed * with or without a value at any time, but only once. This completion is replayed to late subscribers. - * Calling {@link One#emitValue(Object, Sinks.EmitFailureHandler)} (or + * Calling {@link One#emitValue(Object, EmitFailureHandler)} (or * {@link One#tryEmitValue(Object)}) is enough and will implicitly produce * a {@link Subscriber#onComplete()} signal as well. *

      * Use {@link One#asMono()} to expose the {@link Mono} view of the sink to downstream consumers. */ - Sinks.One one(); + One one(); /** - * Help building {@link Sinks.Many} sinks that will broadcast multiple signals to one or more {@link Subscriber}. + * Help building {@link Many} sinks that will broadcast multiple signals to one or more {@link Subscriber}. *

      * Use {@link Many#asFlux()} to expose the {@link Flux} view of the sink to the downstream consumers. * * @return {@link ManySpec} */ ManySpec many(); + + /** + * Help building {@link ManyWithUpstream} sinks that can also be {@link ManyWithUpstream#subscribeTo(Publisher) subscribed to} + * an upstream {@link Publisher}. This is an advanced use case, see {@link ManyWithUpstream#subscribeTo(Publisher)}. + * + * @return a {@link ManyWithUpstreamUnsafeSpec} + */ + ManyWithUpstreamUnsafeSpec manyWithUpstream(); } /** - * Provides {@link Sinks.Many} specs for sinks which can emit multiple elements + * Provides {@link Many} specs for sinks which can emit multiple elements */ public interface ManySpec { /** - * Help building {@link Sinks.Many} that will broadcast signals to a single {@link Subscriber} + * Help building {@link Many} that will broadcast signals to a single {@link Subscriber} * * @return {@link UnicastSpec} */ UnicastSpec unicast(); /** - * Help building {@link Sinks.Many} that will broadcast signals to multiple {@link Subscriber} + * Help building {@link Many} that will broadcast signals to multiple {@link Subscriber} * * @return {@link MulticastSpec} */ MulticastSpec multicast(); /** - * Help building {@link Sinks.Many} that will broadcast signals to multiple {@link Subscriber} with the ability to retain + * Help building {@link Many} that will broadcast signals to multiple {@link Subscriber} with the ability to retain * and replay all or an arbitrary number of elements. * * @return {@link MulticastReplaySpec} @@ -321,27 +367,83 @@ } /** + * Instead of {@link Sinks#unsafe() unsafe} flavors of {@link Many}, this spec provides {@link ManyWithUpstream} + * implementations. These additionally support being subscribed to an upstream {@link Publisher}, at most once. + * Please note that when this is done, one MUST stop using emit/tryEmit APIs, reserving signal creation to be the + * sole responsibility of the upstream {@link Publisher}. + *

      + * As the number of such implementations is deliberately kept low, this spec doesn't further distinguish between + * multicast/unicast/replay categories other than in method naming. + */ + public interface ManyWithUpstreamUnsafeSpec { + /** + * A {@link ManyWithUpstream} with the following characteristics: + *

        + *
      • Multicast
      • + *
      • Without {@link Subscriber}: warm up. Remembers up to {@link Queues#SMALL_BUFFER_SIZE} + * elements pushed via {@link Many#tryEmitNext(Object)} before the first {@link Subscriber} is registered.
      • + *
      • Backpressure : this sink honors downstream demand by conforming to the lowest demand in case + * of multiple subscribers.
        If the difference between multiple subscribers is greater than {@link Queues#SMALL_BUFFER_SIZE}: + *
        • {@link Many#tryEmitNext(Object) tryEmitNext} will return {@link EmitResult#FAIL_OVERFLOW}
        • + *
        • {@link Many#emitNext(Object, EmitFailureHandler) emitNext} will terminate the sink by {@link Many#emitError(Throwable, EmitFailureHandler) emitting} + * an {@link Exceptions#failWithOverflow() overflow error}.
        + *
      • + *
      • Replaying: No replay of values seen by earlier subscribers. Only forwards to a {@link Subscriber} + * the elements that have been pushed to the sink AFTER this subscriber was subscribed, or elements + * that have been buffered due to backpressure/warm up.
      • + *
      + *

      + * + */ + ManyWithUpstream multicastOnBackpressureBuffer(); + + /** + * A {@link ManyWithUpstream} with the following characteristics: + *

        + *
      • Multicast
      • + *
      • Without {@link Subscriber}: warm up. Remembers up to {@code bufferSize} + * elements pushed via {@link Many#tryEmitNext(Object)} before the first {@link Subscriber} is registered.
      • + *
      • Backpressure : this sink honors downstream demand by conforming to the lowest demand in case + * of multiple subscribers.
        If the difference between multiple subscribers is too high compared to {@code bufferSize}: + *
        • {@link Many#tryEmitNext(Object) tryEmitNext} will return {@link EmitResult#FAIL_OVERFLOW}
        • + *
        • {@link Many#emitNext(Object, EmitFailureHandler) emitNext} will terminate the sink by {@link Many#emitError(Throwable, EmitFailureHandler) emitting} + * an {@link Exceptions#failWithOverflow() overflow error}.
        + *
      • + *
      • Replaying: No replay of values seen by earlier subscribers. Only forwards to a {@link Subscriber} + * the elements that have been pushed to the sink AFTER this subscriber was subscribed, or elements + * that have been buffered due to backpressure/warm up.
      • + *
      + *

      + * + * + * @param bufferSize the maximum queue size + * @param autoCancel should the sink fully shutdowns (not publishing anymore) when the last subscriber cancels + */ + ManyWithUpstream multicastOnBackpressureBuffer(int bufferSize, boolean autoCancel); + } + + /** * Provides unicast: 1 sink, 1 {@link Subscriber} */ public interface UnicastSpec { /** - * A {@link Sinks.Many} with the following characteristics: + * A {@link Many} with the following characteristics: *

        - *
      • Unicast: contrary to most other {@link Sinks.Many}, the + *
      • Unicast: contrary to most other {@link Many}, the * {@link Flux} view rejects {@link Subscriber subscribers} past the first one.
      • *
      • Backpressure : this sink honors downstream demand of its single {@link Subscriber}.
      • *
      • Replaying: non-applicable, since only one {@link Subscriber} can register.
      • *
      • Without {@link Subscriber}: all elements pushed to this sink are remembered and will * be replayed once the {@link Subscriber} subscribes.
      • *
      */ - Sinks.Many onBackpressureBuffer(); + Many onBackpressureBuffer(); /** - * A {@link Sinks.Many} with the following characteristics: + * A {@link Many} with the following characteristics: *
        - *
      • Unicast: contrary to most other {@link Sinks.Many}, the + *
      • Unicast: contrary to most other {@link Many}, the * {@link Flux} view rejects {@link Subscriber subscribers} past the first one.
      • *
      • Backpressure : this sink honors downstream demand of its single {@link Subscriber}.
      • *
      • Replaying: non-applicable, since only one {@link Subscriber} can register.
      • @@ -351,12 +453,12 @@ * * @param queue an arbitrary queue to use that must at least support Single Producer / Single Consumer semantics */ - Sinks.Many onBackpressureBuffer(Queue queue); + Many onBackpressureBuffer(Queue queue); /** - * A {@link Sinks.Many} with the following characteristics: + * A {@link Many} with the following characteristics: *
          - *
        • Unicast: contrary to most other {@link Sinks.Many}, the + *
        • Unicast: contrary to most other {@link Many}, the * {@link Flux} view rejects {@link Subscriber subscribers} past the first one.
        • *
        • Backpressure : this sink honors downstream demand of its single {@link Subscriber}.
        • *
        • Replaying: non-applicable, since only one {@link Subscriber} can register.
        • @@ -367,19 +469,19 @@ * @param queue an arbitrary queue to use that must at least support Single Producer / Single Consumer semantics * @param endCallback when a terminal signal is observed: error, complete or cancel */ - Sinks.Many onBackpressureBuffer(Queue queue, Disposable endCallback); + Many onBackpressureBuffer(Queue queue, Disposable endCallback); /** - * A {@link Sinks.Many} with the following characteristics: + * A {@link Many} with the following characteristics: *
            - *
          • Unicast: contrary to most other {@link Sinks.Many}, the + *
          • Unicast: contrary to most other {@link Many}, the * {@link Flux} view rejects {@link Subscriber subscribers} past the first one.
          • *
          • Backpressure : this sink honors downstream demand of the Subscriber, and will emit {@link Subscriber#onError(Throwable)} if there is a mismatch.
          • *
          • Replaying: No replay. Only forwards to a {@link Subscriber} the elements that have been * pushed to the sink AFTER this subscriber was subscribed.
          • *
          */ - Sinks.Many onBackpressureError(); + Many onBackpressureError(); } /** @@ -388,15 +490,15 @@ public interface MulticastSpec { /** - * A {@link Sinks.Many} with the following characteristics: + * A {@link Many} with the following characteristics: *
            *
          • Multicast
          • *
          • Without {@link Subscriber}: warm up. Remembers up to {@link Queues#SMALL_BUFFER_SIZE} * elements pushed via {@link Many#tryEmitNext(Object)} before the first {@link Subscriber} is registered.
          • *
          • Backpressure : this sink honors downstream demand by conforming to the lowest demand in case * of multiple subscribers.
            If the difference between multiple subscribers is greater than {@link Queues#SMALL_BUFFER_SIZE}: *
            • {@link Many#tryEmitNext(Object) tryEmitNext} will return {@link EmitResult#FAIL_OVERFLOW}
            • - *
            • {@link Many#emitNext(Object, Sinks.EmitFailureHandler) emitNext} will terminate the sink by {@link Many#emitError(Throwable, Sinks.EmitFailureHandler) emitting} + *
            • {@link Many#emitNext(Object, EmitFailureHandler) emitNext} will terminate the sink by {@link Many#emitError(Throwable, EmitFailureHandler) emitting} * an {@link Exceptions#failWithOverflow() overflow error}.
            *
          • *
          • Replaying: No replay of values seen by earlier subscribers. Only forwards to a {@link Subscriber} @@ -406,18 +508,18 @@ *

            * */ - Sinks.Many onBackpressureBuffer(); + Many onBackpressureBuffer(); /** - * A {@link Sinks.Many} with the following characteristics: + * A {@link Many} with the following characteristics: *

              *
            • Multicast
            • *
            • Without {@link Subscriber}: warm up. Remembers up to {@code bufferSize} * elements pushed via {@link Many#tryEmitNext(Object)} before the first {@link Subscriber} is registered.
            • *
            • Backpressure : this sink honors downstream demand by conforming to the lowest demand in case * of multiple subscribers.
              If the difference between multiple subscribers is too high compared to {@code bufferSize}: *
              • {@link Many#tryEmitNext(Object) tryEmitNext} will return {@link EmitResult#FAIL_OVERFLOW}
              • - *
              • {@link Many#emitNext(Object, Sinks.EmitFailureHandler) emitNext} will terminate the sink by {@link Many#emitError(Throwable, Sinks.EmitFailureHandler) emitting} + *
              • {@link Many#emitNext(Object, EmitFailureHandler) emitNext} will terminate the sink by {@link Many#emitError(Throwable, EmitFailureHandler) emitting} * an {@link Exceptions#failWithOverflow() overflow error}.
              *
            • *
            • Replaying: No replay of values seen by earlier subscribers. Only forwards to a {@link Subscriber} @@ -429,18 +531,18 @@ * * @param bufferSize the maximum queue size */ - Sinks.Many onBackpressureBuffer(int bufferSize); + Many onBackpressureBuffer(int bufferSize); /** - * A {@link Sinks.Many} with the following characteristics: + * A {@link Many} with the following characteristics: *
                *
              • Multicast
              • *
              • Without {@link Subscriber}: warm up. Remembers up to {@code bufferSize} * elements pushed via {@link Many#tryEmitNext(Object)} before the first {@link Subscriber} is registered.
              • *
              • Backpressure : this sink honors downstream demand by conforming to the lowest demand in case * of multiple subscribers.
                If the difference between multiple subscribers is too high compared to {@code bufferSize}: *
                • {@link Many#tryEmitNext(Object) tryEmitNext} will return {@link EmitResult#FAIL_OVERFLOW}
                • - *
                • {@link Many#emitNext(Object, Sinks.EmitFailureHandler) emitNext} will terminate the sink by {@link Many#emitError(Throwable, Sinks.EmitFailureHandler) emitting} + *
                • {@link Many#emitNext(Object, EmitFailureHandler) emitNext} will terminate the sink by {@link Many#emitError(Throwable, EmitFailureHandler) emitting} * an {@link Exceptions#failWithOverflow() overflow error}.
                *
              • *
              • Replaying: No replay of values seen by earlier subscribers. Only forwards to a {@link Subscriber} @@ -453,10 +555,10 @@ * @param bufferSize the maximum queue size * @param autoCancel should the sink fully shutdowns (not publishing anymore) when the last subscriber cancels */ - Sinks.Many onBackpressureBuffer(int bufferSize, boolean autoCancel); + Many onBackpressureBuffer(int bufferSize, boolean autoCancel); /** - A {@link Sinks.Many} with the following characteristics: + A {@link Many} with the following characteristics: *
                  *
                • Multicast
                • *
                • Without {@link Subscriber}: fail fast on {@link Many#tryEmitNext(Object) tryEmitNext}.
                • @@ -472,12 +574,12 @@ * * * @param the type of elements to emit - * @return a multicast {@link Sinks.Many} that "drops" in case any subscriber is too slow + * @return a multicast {@link Many} that "drops" in case any subscriber is too slow */ - Sinks.Many directAllOrNothing(); + Many directAllOrNothing(); /** - A {@link Sinks.Many} with the following characteristics: + A {@link Many} with the following characteristics: *
                    *
                  • Multicast
                  • *
                  • Without {@link Subscriber}: fail fast on {@link Many#tryEmitNext(Object) tryEmitNext}.
                  • @@ -493,17 +595,17 @@ * * * @param the type of elements to emit - * @return a multicast {@link Sinks.Many} that "drops" in case of no demand from any subscriber + * @return a multicast {@link Many} that "drops" in case of no demand from any subscriber */ - Sinks.Many directBestEffort(); + Many directBestEffort(); } /** * Provides multicast with history/replay capacity : 1 sink, N {@link Subscriber} */ public interface MulticastReplaySpec { /** - * A {@link Sinks.Many} with the following characteristics: + * A {@link Many} with the following characteristics: *
                      *
                    • Multicast
                    • *
                    • Without {@link Subscriber}: all elements pushed to this sink are remembered, @@ -512,10 +614,10 @@ *
                    • Replaying: all elements pushed to this sink are replayed to new subscribers.
                    • *
                    */ - Sinks.Many all(); + Many all(); /** - * A {@link Sinks.Many} with the following characteristics: + * A {@link Many} with the following characteristics: *
                      *
                    • Multicast
                    • *
                    • Without {@link Subscriber}: all elements pushed to this sink are remembered, @@ -525,10 +627,10 @@ *
                    * @param batchSize the underlying buffer will optimize storage by linked arrays of given size */ - Sinks.Many all(int batchSize); + Many all(int batchSize); /** - * A {@link Sinks.Many} with the following characteristics: + * A {@link Many} with the following characteristics: *
                      *
                    • Multicast
                    • *
                    • Without {@link Subscriber}: the latest element pushed to this sink are remembered, @@ -537,10 +639,10 @@ *
                    • Replaying: the latest element pushed to this sink is replayed to new subscribers.
                    • *
                    */ - Sinks.Many latest(); + Many latest(); /** - * A {@link Sinks.Many} with the following characteristics: + * A {@link Many} with the following characteristics: *
                      *
                    • Multicast
                    • *
                    • Without {@link Subscriber}: the latest element pushed to this sink are remembered, @@ -551,10 +653,10 @@ * * @param value default value if there is no latest element to replay */ - Sinks.Many latestOrDefault(T value); + Many latestOrDefault(T value); /** - * A {@link Sinks.Many} with the following characteristics: + * A {@link Many} with the following characteristics: *
                        *
                      • Multicast
                      • *
                      • Without {@link Subscriber}: up to {@code historySize} elements pushed to this sink are remembered, @@ -569,10 +671,10 @@ * * @param historySize maximum number of elements able to replayed, strictly positive */ - Sinks.Many limit(int historySize); + Many limit(int historySize); /** - * A {@link Sinks.Many} with the following characteristics: + * A {@link Many} with the following characteristics: *
                          *
                        • Multicast
                        • *
                        • Without {@link Subscriber}: all elements pushed to this sink are remembered until their {@code maxAge} is reached, @@ -584,10 +686,10 @@ * * @param maxAge maximum retention time for elements to be retained */ - Sinks.Many limit(Duration maxAge); + Many limit(Duration maxAge); /** - * A {@link Sinks.Many} with the following characteristics: + * A {@link Many} with the following characteristics: *
                            *
                          • Multicast
                          • *
                          • Without {@link Subscriber}: all elements pushed to this sink are remembered until their {@code maxAge} is reached, @@ -601,10 +703,10 @@ * @param maxAge maximum retention time for elements to be retained * @param scheduler a {@link Scheduler} to derive the time from */ - Sinks.Many limit(Duration maxAge, Scheduler scheduler); + Many limit(Duration maxAge, Scheduler scheduler); /** - * A {@link Sinks.Many} with the following characteristics: + * A {@link Many} with the following characteristics: *
                              *
                            • Multicast
                            • *
                            • Without {@link Subscriber}: up to {@code historySize} elements pushed to this sink are remembered, @@ -621,10 +723,10 @@ * @param historySize maximum number of elements able to replayed, strictly positive * @param maxAge maximum retention time for elements to be retained */ - Sinks.Many limit(int historySize, Duration maxAge); + Many limit(int historySize, Duration maxAge); /** - * A {@link Sinks.Many} with the following characteristics: + * A {@link Many} with the following characteristics: *
                                *
                              • Multicast
                              • *
                              • Without {@link Subscriber}: up to {@code historySize} elements pushed to this sink are remembered, @@ -642,7 +744,7 @@ * @param maxAge maximum retention time for elements to be retained * @param scheduler a {@link Scheduler} to derive the time from */ - Sinks.Many limit(int historySize, Duration maxAge, Scheduler scheduler); + Many limit(int historySize, Duration maxAge, Scheduler scheduler); } /** @@ -716,7 +818,7 @@ *
                              • *
                              • * {@link EmitResult#FAIL_OVERFLOW}: discard the value ({@link Operators#onDiscard(Object, Context)}) - * then call {@link #emitError(Throwable, Sinks.EmitFailureHandler)} with a {@link Exceptions#failWithOverflow(String)} exception. + * then call {@link #emitError(Throwable, EmitFailureHandler)} with a {@link Exceptions#failWithOverflow(String)} exception. *
                              • *
                              • * {@link EmitResult#FAIL_CANCELLED}: discard the value ({@link Operators#onDiscard(Object, Context)}). @@ -853,12 +955,39 @@ /** * Return a {@link Flux} view of this sink. Every call returns the same instance. * - * @return the {@link Flux} view associated to this {@link Sinks.Many} + * @return the {@link Flux} view associated to this {@link Many} */ Flux asFlux(); } /** + * A {@link Many} which additionally allows being subscribed to an upstream {@link Publisher}, + * which is an advanced pattern requiring external synchronization. See {@link #subscribeTo(Publisher)}} for more details. + * + * @param the type of data emitted by the sink + */ + public interface ManyWithUpstream extends Many { + + /** + * Explicitly subscribe this {@link Many} to an upstream {@link Publisher} without + * exposing it as a {@link Subscriber} at all. + *

                                + * Note that when this is done, one MUST stop using emit/tryEmit APIs, reserving signal + * creation to be the sole responsibility of the upstream {@link Publisher}. + *

                                + * The returned {@link Disposable} provides a way of both unsubscribing from the upstream + * and terminating the sink: currently registered subscribers downstream receive an {@link Subscriber#onError(Throwable) onError} + * signal with a {@link java.util.concurrent.CancellationException} and further attempts at subscribing + * to the sink will trigger a similar signal immediately (in which case the returned {@link Disposable} might be no-op). + *

                                + * Any attempt at subscribing the same {@link ManyWithUpstream} multiple times throws an {@link IllegalStateException} + * indicating that the subscription must be unique. + */ + Disposable subscribeTo(Publisher upstream); + + } + + /** * A base interface for standalone {@link Sinks} with complete-or-fail semantics. *

                                * The sink can be exposed to consuming code as a {@link Mono} via its {@link #asMono()} view. @@ -877,7 +1006,7 @@ * example of how each of these can be dealt with, to decide if the emit API would be a good enough fit instead. * * @return an {@link EmitResult}, which should be checked to distinguish different possible failures - * @see #emitEmpty(Sinks.EmitFailureHandler) + * @see #emitEmpty(EmitFailureHandler) * @see Subscriber#onComplete() */ EmitResult tryEmitEmpty(); @@ -891,7 +1020,7 @@ * * @param error the exception to signal, not null * @return an {@link EmitResult}, which should be checked to distinguish different possible failures - * @see #emitError(Throwable, Sinks.EmitFailureHandler) + * @see #emitError(Throwable, EmitFailureHandler) * @see Subscriber#onError(Throwable) */ EmitResult tryEmitError(Throwable error); @@ -1006,7 +1135,7 @@ /** * Return a {@link Mono} view of this sink. Every call returns the same instance. * - * @return the {@link Mono} view associated to this {@link Sinks.One} + * @return the {@link Mono} view associated to this {@link One} */ Mono asMono(); } @@ -1035,7 +1164,7 @@ * * @param value the value to emit and complete with, or {@code null} to only trigger an onComplete * @return an {@link EmitResult}, which should be checked to distinguish different possible failures - * @see #emitValue(Object, Sinks.EmitFailureHandler) + * @see #emitValue(Object, EmitFailureHandler) * @see Subscriber#onNext(Object) * @see Subscriber#onComplete() */ @@ -1061,7 +1190,7 @@ *

                              • *
                              • * {@link EmitResult#FAIL_OVERFLOW}: discard the value ({@link Operators#onDiscard(Object, Context)}) - * then call {@link #emitError(Throwable, Sinks.EmitFailureHandler)} with a {@link Exceptions#failWithOverflow(String)} exception. + * then call {@link #emitError(Throwable, EmitFailureHandler)} with a {@link Exceptions#failWithOverflow(String)} exception. *
                              • *
                              • * {@link EmitResult#FAIL_CANCELLED}: discard the value ({@link Operators#onDiscard(Object, Context)}). @@ -1089,4 +1218,4 @@ */ void emitValue(@Nullable T value, EmitFailureHandler failureHandler); } -} +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/SinksSpecs.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/SinksSpecs.java (.../SinksSpecs.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/SinksSpecs.java (.../SinksSpecs.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2020-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,11 +26,12 @@ import reactor.core.publisher.Sinks.Many; import reactor.core.publisher.Sinks.One; import reactor.core.scheduler.Scheduler; +import reactor.util.concurrent.Queues; final class SinksSpecs { - static final Sinks.RootSpec UNSAFE_ROOT_SPEC = new RootSpecImpl(false); - static final Sinks.RootSpec DEFAULT_ROOT_SPEC = new RootSpecImpl(true); + static final Sinks.RootSpec UNSAFE_ROOT_SPEC = new UnsafeSpecImpl(); + static final DefaultSinksSpecs DEFAULT_SINKS = new DefaultSinksSpecs(); abstract static class AbstractSerializedSink { @@ -61,53 +62,163 @@ } } - static final class RootSpecImpl implements Sinks.RootSpec, - Sinks.ManySpec, - Sinks.MulticastSpec, - Sinks.MulticastReplaySpec { - final boolean serialized; + static final class UnsafeSpecImpl + implements Sinks.RootSpec, Sinks.ManySpec, Sinks.ManyWithUpstreamUnsafeSpec, Sinks.MulticastSpec, Sinks.MulticastReplaySpec { + + final Sinks.UnicastSpec unicastSpec; + + UnsafeSpecImpl() { + this.unicastSpec = new UnicastSpecImpl(false); + } + + @Override + public Empty empty() { + return new SinkEmptyMulticast<>(); + } + + @Override + public One one() { + return new SinkOneMulticast<>(); + } + + @Override + public Sinks.ManySpec many() { + return this; + } + + @Override + public Sinks.UnicastSpec unicast() { + return this.unicastSpec; + } + + @Override + public Sinks.MulticastSpec multicast() { + return this; + } + + @Override + public Sinks.MulticastReplaySpec replay() { + return this; + } + + @Override + public Sinks.ManyWithUpstreamUnsafeSpec manyWithUpstream() { + return this; + } + + @Override + public Many onBackpressureBuffer() { + return new SinkManyEmitterProcessor<>(true, Queues.SMALL_BUFFER_SIZE); + } + + @Override + public Many onBackpressureBuffer(int bufferSize) { + return new SinkManyEmitterProcessor<>(true, bufferSize); + } + + @Override + public Many onBackpressureBuffer(int bufferSize, boolean autoCancel) { + return new SinkManyEmitterProcessor<>(autoCancel, bufferSize); + } + + @Override + public Many directAllOrNothing() { + return new SinkManyBestEffort<>(true); + } + + @Override + public Many directBestEffort() { + return new SinkManyBestEffort<>(false); + } + + @Override + public Many all() { + return SinkManyReplayProcessor.create(); + } + + @Override + public Many all(int batchSize) { + return SinkManyReplayProcessor.create(batchSize); + } + + @Override + public Many latest() { + return SinkManyReplayProcessor.cacheLast(); + } + + @Override + public Many latestOrDefault(T value) { + return SinkManyReplayProcessor.cacheLastOrDefault(value); + } + + @Override + public Many limit(int historySize) { + return SinkManyReplayProcessor.create(historySize); + } + + @Override + public Many limit(Duration maxAge) { + return SinkManyReplayProcessor.createTimeout(maxAge); + } + + @Override + public Many limit(Duration maxAge, Scheduler scheduler) { + return SinkManyReplayProcessor.createTimeout(maxAge, scheduler); + } + + @Override + public Many limit(int historySize, Duration maxAge) { + return SinkManyReplayProcessor.createSizeAndTimeout(historySize, maxAge); + } + + @Override + public Many limit(int historySize, Duration maxAge, Scheduler scheduler) { + return SinkManyReplayProcessor.createSizeAndTimeout(historySize, maxAge, scheduler); + } + + @Override + public Sinks.ManyWithUpstream multicastOnBackpressureBuffer() { + return new SinkManyEmitterProcessor<>(true, Queues.SMALL_BUFFER_SIZE); + } + + @Override + public Sinks.ManyWithUpstream multicastOnBackpressureBuffer(int bufferSize, boolean autoCancel) { + return new SinkManyEmitterProcessor<>(autoCancel, bufferSize); + } + } + + //Note: RootSpec is now reserved for Sinks.unsafe() + static final class DefaultSinksSpecs implements Sinks.ManySpec, Sinks.MulticastSpec, Sinks.MulticastReplaySpec { + final Sinks.UnicastSpec unicastSpec; //needed because UnicastSpec method names overlap with MulticastSpec - RootSpecImpl(boolean serialized) { - this.serialized = serialized; - //there will only be as many instances of UnicastSpecImpl as there are RootSpecImpl instances (2) - this.unicastSpec = new UnicastSpecImpl(serialized); + DefaultSinksSpecs() { + //there will only one instance of serialized UnicastSpecImpl as there is only one instance of SafeRootSpecImpl + this.unicastSpec = new UnicastSpecImpl(true); } & ContextHolder> Empty wrapEmpty(EMPTY original) { - if (serialized) { - return new SinkEmptySerialized<>(original, original); - } - return original; + return new SinkEmptySerialized<>(original, original); } & ContextHolder> One wrapOne(ONE original) { - if (serialized) { - return new SinkOneSerialized<>(original, original); - } - return original; + return new SinkOneSerialized<>(original, original); } & ContextHolder> Many wrapMany(MANY original) { - if (serialized) { - return new SinkManySerialized<>(original, original); - } - return original; + return new SinkManySerialized<>(original, original); } - @Override - public Sinks.ManySpec many() { + Sinks.ManySpec many() { return this; } - @Override - public Empty empty() { + Empty empty() { return wrapEmpty(new SinkEmptyMulticast<>()); } - @Override - public One one() { + One one() { return wrapOne(new SinkOneMulticast<>()); } @@ -128,23 +239,17 @@ @Override public Many onBackpressureBuffer() { - @SuppressWarnings("deprecation") // EmitterProcessor will be removed in 3.5. - final EmitterProcessor original = EmitterProcessor.create(); - return wrapMany(original); + return wrapMany(new SinkManyEmitterProcessor<>(true, Queues.SMALL_BUFFER_SIZE)); } @Override public Many onBackpressureBuffer(int bufferSize) { - @SuppressWarnings("deprecation") // EmitterProcessor will be removed in 3.5. - final EmitterProcessor original = EmitterProcessor.create(bufferSize); - return wrapMany(original); + return wrapMany(new SinkManyEmitterProcessor<>(true, bufferSize)); } @Override public Many onBackpressureBuffer(int bufferSize, boolean autoCancel) { - @SuppressWarnings("deprecation") // EmitterProcessor will be removed in 3.5. - final EmitterProcessor original = EmitterProcessor.create(bufferSize, autoCancel); - return wrapMany(original); + return wrapMany(new SinkManyEmitterProcessor<>(autoCancel, bufferSize)); } @Override @@ -162,29 +267,25 @@ @Override public Many all() { - @SuppressWarnings("deprecation") // ReplayProcessor will be removed in 3.5. - final ReplayProcessor original = ReplayProcessor.create(); + final SinkManyReplayProcessor original = SinkManyReplayProcessor.create(); return wrapMany(original); } @Override public Many all(int batchSize) { - @SuppressWarnings("deprecation") // ReplayProcessor will be removed in 3.5 - final ReplayProcessor original = ReplayProcessor.create(batchSize, true); + final SinkManyReplayProcessor original = SinkManyReplayProcessor.create(batchSize, true); return wrapMany(original); } @Override public Many latest() { - @SuppressWarnings("deprecation") // ReplayProcessor will be removed in 3.5. - final ReplayProcessor original = ReplayProcessor.cacheLast(); + final SinkManyReplayProcessor original = SinkManyReplayProcessor.cacheLast(); return wrapMany(original); } @Override public Many latestOrDefault(T value) { - @SuppressWarnings("deprecation") // ReplayProcessor will be removed in 3.5. - final ReplayProcessor original = ReplayProcessor.cacheLastOrDefault(value); + final SinkManyReplayProcessor original = SinkManyReplayProcessor.cacheLastOrDefault(value); return wrapMany(original); } @@ -193,22 +294,19 @@ if (historySize <= 0) { throw new IllegalArgumentException("historySize must be > 0"); } - @SuppressWarnings("deprecation") // ReplayProcessor will be removed in 3.5. - final ReplayProcessor original = ReplayProcessor.create(historySize); + final SinkManyReplayProcessor original = SinkManyReplayProcessor.create(historySize); return wrapMany(original); } @Override public Many limit(Duration maxAge) { - @SuppressWarnings("deprecation") // ReplayProcessor will be removed in 3.5. - final ReplayProcessor original = ReplayProcessor.createTimeout(maxAge); + final SinkManyReplayProcessor original = SinkManyReplayProcessor.createTimeout(maxAge); return wrapMany(original); } @Override public Many limit(Duration maxAge, Scheduler scheduler) { - @SuppressWarnings("deprecation") // ReplayProcessor will be removed in 3.5. - final ReplayProcessor original = ReplayProcessor.createTimeout(maxAge, scheduler); + final SinkManyReplayProcessor original = SinkManyReplayProcessor.createTimeout(maxAge, scheduler); return wrapMany(original); } @@ -217,8 +315,7 @@ if (historySize <= 0) { throw new IllegalArgumentException("historySize must be > 0"); } - @SuppressWarnings("deprecation") // ReplayProcessor will be removed in 3.5. - final ReplayProcessor original = ReplayProcessor.createSizeAndTimeout(historySize, maxAge); + final SinkManyReplayProcessor original = SinkManyReplayProcessor.createSizeAndTimeout(historySize, maxAge); return wrapMany(original); } @@ -227,8 +324,7 @@ if (historySize <= 0) { throw new IllegalArgumentException("historySize must be > 0"); } - @SuppressWarnings("deprecation") // ReplayProcessor will be removed in 3.5. - final ReplayProcessor original = ReplayProcessor.createSizeAndTimeout(historySize, maxAge, scheduler); + final SinkManyReplayProcessor original = SinkManyReplayProcessor.createSizeAndTimeout(historySize, maxAge, scheduler); return wrapMany(original); } } @@ -250,30 +346,26 @@ @Override public Many onBackpressureBuffer() { - @SuppressWarnings("deprecation") // UnicastProcessor will be removed in 3.5. - final UnicastProcessor original = UnicastProcessor.create(); + final SinkManyUnicast original = SinkManyUnicast.create(); return wrapMany(original); } @Override public Many onBackpressureBuffer(Queue queue) { - @SuppressWarnings("deprecation") // UnicastProcessor will be removed in 3.5. - final UnicastProcessor original = UnicastProcessor.create(queue); + final SinkManyUnicast original = SinkManyUnicast.create(queue); return wrapMany(original); } @Override public Many onBackpressureBuffer(Queue queue, Disposable endCallback) { - @SuppressWarnings("deprecation") // UnicastProcessor will be removed in 3.5. - final UnicastProcessor original = UnicastProcessor.create(queue, endCallback); + final SinkManyUnicast original = SinkManyUnicast.create(queue, endCallback); return wrapMany(original); } @Override public Many onBackpressureError() { - final UnicastManySinkNoBackpressure original = UnicastManySinkNoBackpressure.create(); + final SinkManyUnicastNoBackpressure original = SinkManyUnicastNoBackpressure.create(); return wrapMany(original); } } -} - +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/StateLogger.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/StateLogger.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/StateLogger.java (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2022 VMware Inc. or its affiliates, All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reactor.core.publisher; + +import reactor.util.Logger; + +/** + * Implementation of the well formatted states migration logger. + */ +class StateLogger { + + final Logger logger; + + StateLogger(Logger logger) { + this.logger = logger; + } + + void log(String instance, String action, long initialState, long committedState) { + log(instance, action, initialState, committedState, false); + } + + void log(String instance, + String action, + long initialState, + long committedState, + boolean logStackTrace) { + if (logStackTrace) { + this.logger.trace(String.format("[%s][%s][%s][%s-%s]", + instance, + action, + action, + Thread.currentThread() + .getId(), + formatState(initialState, 64), + formatState(committedState, 64)), new RuntimeException()); + } + else { + this.logger.trace(String.format("[%s][%s][%s][%s-%s]", + instance, + action, + Thread.currentThread() + .getId(), + formatState(initialState, 64), + formatState(committedState, 64))); + } + } + + void log(String instance, String action, int initialState, int committedState) { + log(instance, action, initialState, committedState, false); + } + + void log(String instance, + String action, + int initialState, + int committedState, + boolean logStackTrace) { + if (logStackTrace) { + this.logger.trace(String.format("[%s][%s][%s][%s-%s]", + instance, + action, + action, + Thread.currentThread() + .getId(), + formatState(initialState, 32), + formatState(committedState, 32)), new RuntimeException()); + } + else { + this.logger.trace(String.format("[%s][%s][%s][%s-%s]", + instance, + action, + Thread.currentThread() + .getId(), + formatState(initialState, 32), + formatState(committedState, 32))); + } + } + + static String formatState(long state, int size) { + final String defaultFormat = Long.toBinaryString(state); + final StringBuilder formatted = new StringBuilder(); + final int toPrepend = size - defaultFormat.length(); + for (int i = 0; i < size; i++) { + if (i != 0 && i % 4 == 0) { + formatted.append("_"); + } + if (i < toPrepend) { + formatted.append("0"); + } + else { + formatted.append(defaultFormat.charAt(i - toPrepend)); + } + } + + formatted.insert(0, "0b"); + return formatted.toString(); + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/SynchronousSink.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/SynchronousSink.java (.../SynchronousSink.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/SynchronousSink.java (.../SynchronousSink.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ import org.reactivestreams.Subscriber; import reactor.core.CoreSubscriber; import reactor.util.context.Context; +import reactor.util.context.ContextView; /** * Interface to produce synchronously "one signal" to an underlying {@link Subscriber}. @@ -50,10 +51,25 @@ * {@link CoreSubscriber#currentContext()} * * @return the current subscriber {@link Context}. + * @deprecated To be removed in 3.6.0 at the earliest. Prefer using #contextView() instead. */ + @Deprecated Context currentContext(); /** + * Return the current subscriber's context as a {@link ContextView} for inspection. + *

                                + * {@link Context} can be enriched downstream via {@link Mono#contextWrite(Function)} + * or {@link Flux#contextWrite(Function)} operators or directly by a child subscriber overriding + * {@link CoreSubscriber#currentContext()} + * + * @return the current subscriber {@link ContextView}. + */ + default ContextView contextView() { + return currentContext(); + } + + /** * @param e the exception to signal, not null * * @see Subscriber#onError(Throwable) Fisheye: Tag c4ce08dc0aae7d9da822088a3d5710484f6b0402 refers to a dead (removed) revision in file `3rdParty_sources/reactor/reactor/core/publisher/UnicastManySinkNoBackpressure.java'. Fisheye: No comparison available. Pass `N' to diff? Index: 3rdParty_sources/reactor/reactor/core/publisher/UnicastProcessor.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/UnicastProcessor.java (.../UnicastProcessor.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/UnicastProcessor.java (.../UnicastProcessor.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -88,7 +88,7 @@ * * @param the input and output type * @deprecated to be removed in 3.5, prefer clear cut usage of {@link Sinks} through - * variations under {@link reactor.core.publisher.Sinks.UnicastSpec Sinks.many().unicast()}. + * variations under {@link Sinks.UnicastSpec Sinks.many().unicast()}. */ @Deprecated public final class UnicastProcessor extends FluxProcessor @@ -262,7 +262,7 @@ doTerminate(); drain(null); - return Sinks.EmitResult.OK; + return EmitResult.OK; } @Override @@ -271,9 +271,9 @@ } @Override - public Sinks.EmitResult tryEmitError(Throwable t) { + public EmitResult tryEmitError(Throwable t) { if (done) { - return Sinks.EmitResult.FAIL_TERMINATED; + return EmitResult.FAIL_TERMINATED; } if (cancelled) { return EmitResult.FAIL_CANCELLED; Fisheye: Tag c4ce08dc0aae7d9da822088a3d5710484f6b0402 refers to a dead (removed) revision in file `3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doAfterSuccessOrError.svg'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag c4ce08dc0aae7d9da822088a3d5710484f6b0402 refers to a dead (removed) revision in file `3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnSuccessOrError.svg'. Fisheye: No comparison available. Pass `N' to diff? Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mergeComparing.svg =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mergeComparing.svg (.../mergeComparing.svg) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mergeComparing.svg (.../mergeComparing.svg) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -14,12 +14,11 @@ - - + - + @@ -30,34 +29,33 @@ - + - - - - - + + + + + - - - - 5 - 3 - 2 - 4 - 1 + + + + 5 + 3 + 2 + 4 + 1 5 3 - 2 4 1 - - 0 - - - 0 - + + 0 + + + 0 + mergeComparing( small @@ -70,4 +68,6 @@ ) big + + 2 Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mergeComparingNaturalOrder.svg =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mergeComparingNaturalOrder.svg (.../mergeComparingNaturalOrder.svg) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mergeComparingNaturalOrder.svg (.../mergeComparingNaturalOrder.svg) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -11,49 +11,49 @@ - + - + - + - - + + - + - + - - - - + + + + 1 - 3 - 5 + 3 + 5 2 - 4 + 4 1 3 - 5 + 5 2 4 - - 0 - - - 0 - + + 0 + + + 0 + mergeComparing Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mergeComparingWith.svg =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mergeComparingWith.svg (.../mergeComparingWith.svg) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mergeComparingWith.svg (.../mergeComparingWith.svg) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -15,11 +15,11 @@ - + - + @@ -31,20 +31,20 @@ - + - - + + - + 5 3 2 4 - 1 + 1 5 3 @@ -53,9 +53,9 @@ 1 0 - - - 0 + + + 0 Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mergePriority.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mergePriority.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mergePriority.svg (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + 3 + 2 + 4 + 1 + + 5 + 3 + 4 + 1 + + 0 + + + 0 + + mergePriority( + + small + + big + + , + ( + ) + + ) + big + + 2 + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mergePriorityNaturalOrder.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mergePriorityNaturalOrder.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mergePriorityNaturalOrder.svg (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 3 + 7 + 2 + 4 + + 1 + 3 + 2 + 4 + + 0 + + + 0 + + mergePriority + + 7 + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onErrorCompleteForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onErrorCompleteForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onErrorCompleteForFlux.svg (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -0,0 +1,35 @@ + + + + + + + + + + + + onErrorComplete() + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onErrorCompleteForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onErrorCompleteForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onErrorCompleteForMono.svg (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -0,0 +1,29 @@ + + + + + + + + + + + + onErrorComplete() + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/singleOptional.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/singleOptional.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/singleOptional.svg (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -0,0 +1,1220 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + singleOptional + + + + + + + + + + + + + + + + + + + + + Optional.of(     ) + + Optional.empty() + Fisheye: Tag c4ce08dc0aae7d9da822088a3d5710484f6b0402 refers to a dead (removed) revision in file `3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/take.svg'. Fisheye: No comparison available. Pass `N' to diff? Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/takeLimitRequestFalse.svg =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/takeLimitRequestFalse.svg (.../takeLimitRequestFalse.svg) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/takeLimitRequestFalse.svg (.../takeLimitRequestFalse.svg) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,77 +1,48 @@ - - - - - - + + + + - - - - + + - - - - + + - - - - + + - - - - - - - - - - - - - - - - - - take(3, false) - - - - - - - - - - - - - - cancel() - - - - take(0, false) - - - - - - - - - cancel() - - - - - - - - + + take(3, false) + + + + + + + + + + + + cancel() + + request(max) + + take(0, false) + + + + + + + cancel() + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/takeLimitRequestTrue.svg =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/takeLimitRequestTrue.svg (.../takeLimitRequestTrue.svg) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/takeLimitRequestTrue.svg (.../takeLimitRequestTrue.svg) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,118 +1,63 @@ - - - - - - + + + + - - - - + + - - - - + + - - - - + + - - - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - take(3, true) + + + + + + + + + + + + + + + + take(3, true) - - - - - - request(23) + + + + + + request(23) - - - request(2) + + + request(2) - - - request(2) + + + request(2) - - - request(1) + + + request(1) - - - - cancel() + + + + cancel() Index: 3rdParty_sources/reactor/reactor/core/publisher/package-info.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/publisher/package-info.java (.../package-info.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/publisher/package-info.java (.../package-info.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2011-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,28 +15,15 @@ */ /** - * Provide for - * {@link reactor.core.publisher.Flux}, {@link reactor.core.publisher.Mono} composition - * API and {@link org.reactivestreams.Processor} implementations + * Provide main Reactive APIs in {@link reactor.core.publisher.Flux} and {@link reactor.core.publisher.Mono}, + * as well as various helper classes, interfaces used in the composition API, variants of Flux and operator-building + * utilities. * *

                                Flux

                                * A typed N-elements or zero sequence {@link org.reactivestreams.Publisher} with core reactive extensions. * *

                                Mono

                                * A typed one-element at most sequence {@link org.reactivestreams.Publisher} with core reactive extensions. - * - *

                                Processors

                                - * The following - * {@link org.reactivestreams.Processor} extending {@link reactor.core.publisher.FluxProcessor} are available: - *
                                  - *
                                • A synchronous/non-opinionated pub-sub replaying capable event emitter : - * {@link reactor.core.publisher.EmitterProcessor}, - * {@link reactor.core.publisher.ReplayProcessor}, - * {@link reactor.core.publisher.UnicastProcessor} and - * {@link reactor.core.publisher.DirectProcessor}
                                • - *
                                • {@link reactor.core.publisher.FluxProcessor} itself offers factories to build arbitrary {@link org.reactivestreams.Processor}
                                • - *
                                - *

                                ** * @author Stephane Maldini */ Index: 3rdParty_sources/reactor/reactor/core/scheduler/BoundedElasticScheduler.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/scheduler/BoundedElasticScheduler.java (.../BoundedElasticScheduler.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/scheduler/BoundedElasticScheduler.java (.../BoundedElasticScheduler.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2019-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,23 +16,30 @@ package reactor.core.scheduler; +import reactor.core.Disposable; +import reactor.core.Disposables; +import reactor.core.Exceptions; +import reactor.core.Scannable; +import reactor.core.publisher.Mono; +import reactor.util.Logger; +import reactor.util.Loggers; +import reactor.util.annotation.Nullable; + import java.time.Clock; import java.time.Instant; import java.time.ZoneId; import java.util.ArrayList; import java.util.Collection; -import java.util.Comparator; +import java.util.Collections; import java.util.Deque; import java.util.List; import java.util.Objects; import java.util.PriorityQueue; -import java.util.Queue; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.Future; -import java.util.concurrent.PriorityBlockingQueue; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ScheduledExecutorService; @@ -48,10 +55,8 @@ import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import java.util.stream.Stream; -import reactor.core.Disposable; -import reactor.core.Disposables; -import reactor.core.Exceptions; -import reactor.core.Scannable; +import static reactor.core.scheduler.BoundedElasticScheduler.BoundedServices.CREATING; +import static reactor.core.scheduler.BoundedElasticScheduler.BoundedServices.SHUTDOWN; /** * Scheduler that hosts a pool of 0-N single-threaded {@link BoundedScheduledExecutorService} and exposes workers @@ -64,50 +69,30 @@ * * @author Simon Baslé */ -final class BoundedElasticScheduler implements Scheduler, Scannable { +final class BoundedElasticScheduler implements Scheduler, + SchedulerState.DisposeAwaiter, + Scannable { + static final Logger LOGGER = Loggers.getLogger(BoundedElasticScheduler.class); + static final int DEFAULT_TTL_SECONDS = 60; - static final AtomicLong EVICTOR_COUNTER = new AtomicLong(); + static final AtomicLong COUNTER = new AtomicLong(); - static final ThreadFactory EVICTOR_FACTORY = r -> { - Thread t = new Thread(r, Schedulers.BOUNDED_ELASTIC + "-evictor-" + EVICTOR_COUNTER.incrementAndGet()); - t.setDaemon(true); - return t; - }; - - static final BoundedServices SHUTDOWN; - static final BoundedState CREATING; - - static { - SHUTDOWN = new BoundedServices(); - SHUTDOWN.dispose(); - ScheduledExecutorService s = Executors.newSingleThreadScheduledExecutor(); - s.shutdownNow(); - CREATING = new BoundedState(SHUTDOWN, s) { - @Override - public String toString() { - return "CREATING BoundedState"; - } - }; - CREATING.markCount = -1; //always -1, ensures tryPick never returns true - CREATING.idleSinceTimestamp = -1; //consider evicted - } - final int maxThreads; final int maxTaskQueuedPerThread; final Clock clock; final ThreadFactory factory; final long ttlMillis; - volatile BoundedServices boundedServices; - static final AtomicReferenceFieldUpdater BOUNDED_SERVICES = - AtomicReferenceFieldUpdater.newUpdater(BoundedElasticScheduler.class, BoundedServices.class, "boundedServices"); + volatile SchedulerState state; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater STATE = + AtomicReferenceFieldUpdater.newUpdater(BoundedElasticScheduler.class, SchedulerState.class, "state"); - volatile ScheduledExecutorService evictor; - static final AtomicReferenceFieldUpdater EVICTOR = - AtomicReferenceFieldUpdater.newUpdater(BoundedElasticScheduler.class, ScheduledExecutorService.class, "evictor"); + private static final SchedulerState INIT = + SchedulerState.init(SHUTDOWN); /** * This constructor lets define millisecond-grained TTLs and a custom {@link Clock}, @@ -130,12 +115,12 @@ this.clock = Objects.requireNonNull(clock, "A Clock must be provided"); this.ttlMillis = ttlMillis; - this.boundedServices = SHUTDOWN; //initially disposed, EVICTOR is also null + STATE.lazySet(this, INIT); } /** * Create a {@link BoundedElasticScheduler} with the given configuration. Note that backing threads - * (or executors) can be shared by each {@link reactor.core.scheduler.Scheduler.Worker}, so each worker + * (or executors) can be shared by each {@link Worker}, so each worker * can contribute to the task queue size. * * @param maxThreads the maximum number of backing threads to spawn, must be strictly positive @@ -158,78 +143,205 @@ @Override public boolean isDisposed() { - return BOUNDED_SERVICES.get(this) == SHUTDOWN; + // we only consider disposed as actually shutdown + SchedulerState current = this.state; + return current != INIT && current.currentResource == SHUTDOWN; } @Override - public void start() { - for (;;) { - BoundedServices services = BOUNDED_SERVICES.get(this); - if (services != SHUTDOWN) { + public void init() { + SchedulerState a = this.state; + if (a != INIT) { + if (a.currentResource == SHUTDOWN) { + throw new IllegalStateException( + "Initializing a disposed scheduler is not permitted" + ); + } + // return early - scheduler already initialized + return; + } + + SchedulerState b = + SchedulerState.init(new BoundedServices(this)); + if (STATE.compareAndSet(this, INIT, b)) { + try { + b.currentResource.evictor.scheduleAtFixedRate( + b.currentResource::eviction, + ttlMillis, ttlMillis, TimeUnit.MILLISECONDS + ); return; + } catch (RejectedExecutionException ree) { + // The executor was most likely shut down in parallel. + // If the state is SHUTDOWN - it's ok, no eviction schedule required; + // If it's running - the other thread did a restart and will run its own schedule. + // In both cases we throw an exception, as the caller of init() should + // expect the scheduler to be running when this method returns. + throw new IllegalStateException( + "Scheduler disposed during initialization" + ); } - BoundedServices newServices = new BoundedServices(this); - if (BOUNDED_SERVICES.compareAndSet(this, services, newServices)) { - ScheduledExecutorService e = Executors.newScheduledThreadPool(1, EVICTOR_FACTORY); - if (EVICTOR.compareAndSet(this, null, e)) { - try { - e.scheduleAtFixedRate(newServices::eviction, ttlMillis, ttlMillis, TimeUnit.MILLISECONDS); - } - catch (RejectedExecutionException ree) { - // the executor was most likely shut down in parallel - if (!isDisposed()) { - throw ree; - } // else swallow - } - } - else { - e.shutdownNow(); - } + } else { + b.currentResource.evictor.shutdownNow(); + // Currently, isDisposed() is true for non-initialized state, but that will + // be fixed in 3.5.0. At this stage we know however that the state is no + // longer INIT, so isDisposed() actually means disposed state. + if (isDisposed()) { + throw new IllegalStateException( + "Initializing a disposed scheduler is not permitted" + ); + } + } + } + + @Override + public void start() { + SchedulerState a = this.state; + + if (a.currentResource != SHUTDOWN) { + return; + } + + SchedulerState b = + SchedulerState.init(new BoundedServices(this)); + if (STATE.compareAndSet(this, a, b)) { + try { + b.currentResource.evictor.scheduleAtFixedRate( + b.currentResource::eviction, + ttlMillis, ttlMillis, TimeUnit.MILLISECONDS + ); return; + } catch (RejectedExecutionException ree) { + // The executor was most likely shut down in parallel. + // If the state is SHUTDOWN - it's ok, no eviction schedule required; + // If it's running - the other thread did a restart and will run its own schedule. + // In both cases we ignore it. } } + + // someone else shutdown or started successfully, free the resource + b.currentResource.evictor.shutdownNow(); } @Override + public boolean await(BoundedServices boundedServices, long timeout, TimeUnit timeUnit) throws InterruptedException { + if (!boundedServices.evictor.awaitTermination(timeout, timeUnit)) { + return false; + } + for (BoundedState bs : boundedServices.busyStates.array) { + if (!bs.executor.awaitTermination(timeout, timeUnit)) { + return false; + } + } + return true; + } + + @Override public void dispose() { - BoundedServices services = BOUNDED_SERVICES.get(this); - if (services != SHUTDOWN && BOUNDED_SERVICES.compareAndSet(this, services, SHUTDOWN)) { - ScheduledExecutorService e = EVICTOR.getAndSet(this, null); - if (e != null) { - e.shutdownNow(); + SchedulerState previous = state; + + if (previous.currentResource == SHUTDOWN) { + // A dispose process might be ongoing, but we want a forceful shutdown, + // so we do our best to release the resources without updating the state. + if (previous.initialResource != null) { + previous.initialResource.evictor.shutdownNow(); + for (BoundedState bs : previous.initialResource.busyStates.array) { + bs.shutdown(true); + } } - services.dispose(); + return; } + + final BoundedState[] toAwait = previous.currentResource.dispose(); + SchedulerState shutDown = SchedulerState.transition( + previous.currentResource, + SHUTDOWN, this + ); + + STATE.compareAndSet(this, previous, shutDown); + // If unsuccessful - either another thread disposed or restarted - no issue, + // we only care about the one stored in shutDown. + + assert shutDown.initialResource != null; + shutDown.initialResource.evictor.shutdownNow(); + for (BoundedState bs : toAwait) { + bs.shutdown(true); + } } @Override + public Mono disposeGracefully() { + return Mono.defer(() -> { + SchedulerState previous = state; + + if (previous.currentResource == SHUTDOWN) { + return previous.onDispose; + } + + final BoundedState[] toAwait = previous.currentResource.dispose(); + SchedulerState shutDown = SchedulerState.transition( + previous.currentResource, + SHUTDOWN, this + ); + + STATE.compareAndSet(this, previous, shutDown); + // If unsuccessful - either another thread disposed or restarted - no issue, + // we only care about the one stored in shutDown. + + assert shutDown.initialResource != null; + shutDown.initialResource.evictor.shutdown(); + for (BoundedState bs : toAwait) { + bs.shutdown(false); + } + return shutDown.onDispose; + }); + } + + @Override public Disposable schedule(Runnable task) { //tasks running once will call dispose on the BoundedState, decreasing its usage by one - BoundedState picked = BOUNDED_SERVICES.get(this).pick(); - return Schedulers.directSchedule(picked.executor, task, picked, 0L, TimeUnit.MILLISECONDS); + BoundedState picked = state.currentResource.pick(); + try { + return Schedulers.directSchedule(picked.executor, task, picked, 0L, TimeUnit.MILLISECONDS); + } catch (RejectedExecutionException ex) { + // ensure to free the BoundedState so it can be reused + picked.dispose(); + throw ex; + } } @Override public Disposable schedule(Runnable task, long delay, TimeUnit unit) { //tasks running once will call dispose on the BoundedState, decreasing its usage by one - final BoundedState picked = BOUNDED_SERVICES.get(this).pick(); - return Schedulers.directSchedule(picked.executor, task, picked, delay, unit); + final BoundedState picked = state.currentResource.pick(); + try { + return Schedulers.directSchedule(picked.executor, task, picked, delay, unit); + } catch (RejectedExecutionException ex) { + // ensure to free the BoundedState so it can be reused + picked.dispose(); + throw ex; + } } @Override public Disposable schedulePeriodically(Runnable task, long initialDelay, long period, TimeUnit unit) { - final BoundedState picked = BOUNDED_SERVICES.get(this).pick(); - Disposable scheduledTask = Schedulers.directSchedulePeriodically(picked.executor, - task, - initialDelay, - period, - unit); - //a composite with picked ensures the cancellation of the task releases the BoundedState - // (ie decreases its usage by one) - return Disposables.composite(scheduledTask, picked); + final BoundedState picked = state.currentResource.pick(); + try { + Disposable scheduledTask = Schedulers.directSchedulePeriodically(picked.executor, + task, + initialDelay, + period, + unit); + //a composite with picked ensures the cancellation of the task releases the BoundedState + // (ie decreases its usage by one) + return Disposables.composite(scheduledTask, picked); + } catch (RejectedExecutionException ex) { + // ensure to free the BoundedState so it can be reused + picked.dispose(); + throw ex; + } } @Override @@ -255,21 +367,21 @@ * @return a best effort total count of the spinned up executors */ int estimateSize() { - return BOUNDED_SERVICES.get(this).get(); + return state.currentResource.get(); } /** * @return a best effort total count of the busy executors */ int estimateBusy() { - return BOUNDED_SERVICES.get(this).busyQueue.size(); + return state.currentResource.busyStates.array.length; } /** * @return a best effort total count of the idle executors */ int estimateIdle() { - return BOUNDED_SERVICES.get(this).idleQueue.size(); + return state.currentResource.idleQueue.size(); } /** @@ -278,9 +390,9 @@ * @return the total task capacity, or {@literal -1} if any backing executor's task queue size cannot be instrumented */ int estimateRemainingTaskCapacity() { - Queue busyQueue = BOUNDED_SERVICES.get(this).busyQueue; + BoundedState[] busyArray = state.currentResource.busyStates.array; int totalTaskCapacity = maxTaskQueuedPerThread * maxThreads; - for (BoundedState state : busyQueue) { + for (BoundedState state : busyArray) { int stateQueueSize = state.estimateQueueSize(); if (stateQueueSize >= 0) { totalTaskCapacity -= stateQueueSize; @@ -304,28 +416,30 @@ @Override public Stream inners() { - BoundedServices services = BOUNDED_SERVICES.get(this); - return Stream.concat(services.busyQueue.stream(), services.idleQueue.stream()) + BoundedServices services = state.currentResource; + return Stream.concat(Stream.of(services.busyStates.array), services.idleQueue.stream()) .filter(obj -> obj != null && obj != CREATING); } @Override public Worker createWorker() { - BoundedState picked = BOUNDED_SERVICES.get(this) - .pick(); + BoundedState picked = state.currentResource.pick(); ExecutorServiceWorker worker = new ExecutorServiceWorker(picked.executor); worker.disposables.add(picked); //this ensures the BoundedState will be released when worker is disposed return worker; } + static final class BoundedServices extends AtomicInteger { - static final class BoundedServices extends AtomicInteger implements Disposable { + static final class BusyStates { + final BoundedState[] array; + final boolean shutdown; - /** - * Constant for this counter of live executors to reflect the whole pool has been - * stopped. - */ - static final int DISPOSED = -1; + public BusyStates(BoundedState[] array, boolean shutdown) { + this.array = array; + this.shutdown = shutdown; + } + } /** * The {@link ZoneId} used for clocks. Since the {@link Clock} is only used to ensure @@ -335,27 +449,69 @@ */ static final ZoneId ZONE_UTC = ZoneId.of("UTC"); + static final BusyStates ALL_IDLE = new BusyStates(new BoundedState[0], false); + static final BusyStates ALL_SHUTDOWN = new BusyStates(new BoundedState[0], true); + static final ScheduledExecutorService EVICTOR_SHUTDOWN; + static final BoundedServices SHUTDOWN; + static final BoundedServices SHUTTING_DOWN; + static final BoundedState CREATING; + + static { + EVICTOR_SHUTDOWN = Executors.newSingleThreadScheduledExecutor(); + EVICTOR_SHUTDOWN.shutdownNow(); + + SHUTDOWN = new BoundedServices(); + SHUTTING_DOWN = new BoundedServices(); + SHUTDOWN.dispose(); + SHUTTING_DOWN.dispose(); + + ScheduledExecutorService s = Executors.newSingleThreadScheduledExecutor(); + s.shutdownNow(); + CREATING = new BoundedState(SHUTDOWN, s) { + @Override + public String toString() { + return "CREATING BoundedState"; + } + }; + CREATING.markCount = -1; //always -1, ensures tryPick never returns true + CREATING.idleSinceTimestamp = -1; //consider evicted + } + + static final AtomicLong EVICTOR_COUNTER = new AtomicLong(); + static final ThreadFactory EVICTOR_FACTORY = r -> { + Thread t = new Thread(r, Schedulers.BOUNDED_ELASTIC + "-evictor-" + EVICTOR_COUNTER.incrementAndGet()); + t.setDaemon(true); + return t; + }; + + final BoundedElasticScheduler parent; //duplicated Clock field from parent so that SHUTDOWN can be instantiated and partially used final Clock clock; + final ScheduledExecutorService evictor; final Deque idleQueue; - final PriorityBlockingQueue busyQueue; + volatile BusyStates busyStates; + static final AtomicReferenceFieldUpdater BUSY_STATES = + AtomicReferenceFieldUpdater.newUpdater(BoundedServices.class, BusyStates.class, + "busyStates"); + //constructor for SHUTDOWN private BoundedServices() { this.parent = null; this.clock = Clock.fixed(Instant.EPOCH, ZONE_UTC); - this.busyQueue = new PriorityBlockingQueue<>(); this.idleQueue = new ConcurrentLinkedDeque<>(); + this.busyStates = ALL_SHUTDOWN; + this.evictor = EVICTOR_SHUTDOWN; } BoundedServices(BoundedElasticScheduler parent) { this.parent = parent; this.clock = parent.clock; - this.busyQueue = new PriorityBlockingQueue<>(parent.maxThreads, - Comparator.comparingInt(bs -> bs.markCount)); this.idleQueue = new ConcurrentLinkedDeque<>(); + this.busyStates = ALL_IDLE; + this.evictor = Executors.newSingleThreadScheduledExecutor(EVICTOR_FACTORY); } /** @@ -374,6 +530,79 @@ } /** + * @param bs the state to set busy + * @return true if the {@link BoundedState} could be added to the busy array (ie. we're not shut down), false if shutting down + */ + boolean setBusy(BoundedState bs) { + for (; ; ) { + BusyStates previous = busyStates; + if (previous.shutdown) { + return false; + } + + int len = previous.array.length; + BoundedState[] replacement = new BoundedState[len + 1]; + System.arraycopy(previous.array, 0, replacement, 0, len); + replacement[len] = bs; + + if (BUSY_STATES.compareAndSet(this, previous, + new BusyStates(replacement, false))) { + return true; + } + } + } + + void setIdle(BoundedState boundedState) { + for(;;) { + BusyStates current = busyStates; + BoundedState[] arr = busyStates.array; + int len = arr.length; + + if (len == 0 || current.shutdown) { + return; + } + + BusyStates replacement = null; + if (len == 1) { + if (arr[0] == boundedState) { + replacement = ALL_IDLE; + } + } + else { + for (int i = 0; i < len; i++) { + BoundedState state = arr[i]; + if (state == boundedState) { + replacement = new BusyStates( + new BoundedState[len - 1], false + ); + System.arraycopy(arr, 0, replacement.array, 0, i); + System.arraycopy(arr, i + 1, replacement.array, i, len - i - 1); + break; + } + } + } + if (replacement == null) { + //bounded state not found, ignore + return; + } + if (BUSY_STATES.compareAndSet(this, current, replacement)) { + //impl. note: reversed order could lead to a race condition where state is added to idleQueue + //then concurrently pick()ed into busyQueue then removed from same busyQueue. + this.idleQueue.add(boundedState); + // check whether we missed a shutdown + if (this.busyStates.shutdown) { + // we did, so we make sure the racing adds don't leak + boundedState.shutdown(true); + while ((boundedState = idleQueue.pollLast()) != null) { + boundedState.shutdown(true); + } + } + return; + } + } + } + + /** * Pick a {@link BoundedState}, prioritizing idle ones then spinning up a new one if enough capacity. * Otherwise, picks an active one by taking from a {@link PriorityQueue}. The picking is * optimistically re-attempted if the picked slot cannot be marked as picked. @@ -382,16 +611,20 @@ */ BoundedState pick() { for (;;) { - int a = get(); - if (a == DISPOSED) { + if (busyStates == ALL_SHUTDOWN) { return CREATING; //synonym for shutdown, since the underlying executor is shut down } + int a = get(); if (!idleQueue.isEmpty()) { //try to find an idle resource BoundedState bs = idleQueue.pollLast(); if (bs != null && bs.markPicked()) { - busyQueue.add(bs); + boolean accepted = setBusy(bs); + if (!accepted) { // shutdown in the meantime + bs.shutdown(true); + return CREATING; + } return bs; } //else optimistically retry (implicit continue here) @@ -402,42 +635,83 @@ ScheduledExecutorService s = Schedulers.decorateExecutorService(parent, parent.createBoundedExecutorService()); BoundedState newState = new BoundedState(this, s); if (newState.markPicked()) { - busyQueue.add(newState); + boolean accepted = setBusy(newState); + if (!accepted) { // shutdown in the meantime + newState.shutdown(true); + return CREATING; + } return newState; } } //else optimistically retry (implicit continue here) } else { - //pick the least busy one - BoundedState s = busyQueue.poll(); + BoundedState s = choseOneBusy(); if (s != null && s.markPicked()) { - busyQueue.add(s); //put it back in the queue with updated priority return s; } //else optimistically retry (implicit continue here) } } } - void setIdle(BoundedState boundedState) { - //impl. note: reversed order could lead to a race condition where state is added to idleQueue - //then concurrently pick()ed into busyQueue then removed from same busyQueue. - if (this.busyQueue.remove(boundedState)) { - this.idleQueue.add(boundedState); + @Nullable + private BoundedState choseOneBusy() { + BoundedState[] arr = busyStates.array; + int len = arr.length; + if (len == 0) { + return null; //implicit retry in the pick() loop } - } + if (len == 1) { + return arr[0]; + } - @Override - public boolean isDisposed() { - return get() == DISPOSED; + BoundedState choice = arr[0]; + int leastBusy = Integer.MAX_VALUE; + + for (int i = 0; i < arr.length; i++) { + BoundedState state = arr[i]; + int busy = state.markCount; + if (busy < leastBusy) { + leastBusy = busy; + choice = state; + } + } + return choice; } - @Override - public void dispose() { - set(DISPOSED); - idleQueue.forEach(BoundedState::shutdown); - busyQueue.forEach(BoundedState::shutdown); + public BoundedState[] dispose() { + BusyStates current; + for (;;) { + current = busyStates; + + if (current.shutdown) { + return current.array; + } + + if (BUSY_STATES.compareAndSet(this, + current, new BusyStates(current.array, true))) { + break; + } + // the race can happen also with scheduled tasks and eviction + // so we need to retry if shutdown transition fails + } + + BoundedState[] arr = current.array; + // The idleQueue must be drained first as concurrent removals + // by evictor or additions by finished tasks can invalidate the size + // used if a regular array was created here. + // In case of concurrent calls, it is not needed to atomically drain the + // queue, nor guarantee same BoundedStates are read, as long as the caller + // shuts down the returned BoundedStates. Also, idle ones should easily + // shut down, it's not necessary to distinguish graceful from forceful. + ArrayList toAwait = new ArrayList<>(idleQueue.size() + arr.length); + BoundedState bs; + while ((bs = idleQueue.pollLast()) != null) { + toAwait.add(bs); + } + Collections.addAll(toAwait, arr); + return toAwait.toArray(new BoundedState[0]); } } @@ -523,7 +797,7 @@ * This is called when a worker is done using the executor. {@link #dispose()} is an alias * to this method (for APIs that take a {@link Disposable}). * - * @see #shutdown() + * @see #shutdown(boolean) * @see #dispose() */ void release() { @@ -550,10 +824,14 @@ * @see #release() * @see #dispose() */ - void shutdown() { + void shutdown(boolean now) { this.idleSinceTimestamp = -1L; MARK_COUNT.set(this, EVICTED); - this.executor.shutdownNow(); + if (now) { + this.executor.shutdownNow(); + } else { + this.executor.shutdown(); + } } /** @@ -635,7 +913,7 @@ return "BoundedScheduledExecutorService{" + state + ", queued=" + queued + "/" + queueCapacity + ", completed=" + completed + '}'; } - private void ensureQueueCapacity(int taskCount) { + void ensureQueueCapacity(int taskCount) { if (queueCapacity == Integer.MAX_VALUE) { return; } @@ -819,4 +1097,4 @@ super.submit(command); } } -} +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/scheduler/DelegateServiceScheduler.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/scheduler/DelegateServiceScheduler.java (.../DelegateServiceScheduler.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/scheduler/DelegateServiceScheduler.java (.../DelegateServiceScheduler.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2017-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,11 +16,13 @@ package reactor.core.scheduler; +import java.time.Duration; import java.util.Collection; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; @@ -32,6 +34,7 @@ import reactor.core.Disposable; import reactor.core.Exceptions; import reactor.core.Scannable; +import reactor.core.publisher.Mono; import reactor.util.annotation.NonNull; import reactor.util.annotation.Nullable; @@ -43,32 +46,40 @@ * @author Stephane Maldini * @author Simon Baslé */ -final class DelegateServiceScheduler implements Scheduler, Scannable { +final class DelegateServiceScheduler implements Scheduler, SchedulerState.DisposeAwaiter, Scannable { + static final ScheduledExecutorService TERMINATED; + + static { + TERMINATED = Executors.newSingleThreadScheduledExecutor(); + TERMINATED.shutdownNow(); + } + final String executorName; final ScheduledExecutorService original; @Nullable - volatile ScheduledExecutorService executor; - static final AtomicReferenceFieldUpdater EXECUTOR = - AtomicReferenceFieldUpdater.newUpdater(DelegateServiceScheduler.class, ScheduledExecutorService.class, "executor"); + volatile SchedulerState state; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater STATE = + AtomicReferenceFieldUpdater.newUpdater(DelegateServiceScheduler.class, + SchedulerState.class, "state"); DelegateServiceScheduler(String executorName, ExecutorService executorService) { this.executorName = executorName; this.original = convert(executorService); - this.executor = null; //to be initialized in start() } ScheduledExecutorService getOrCreate() { - ScheduledExecutorService e = executor; - if (e == null) { - start(); - e = executor; - if (e == null) { + SchedulerState s = state; + if (s == null) { + init(); + s = state; + if (s == null) { throw new IllegalStateException("executor is null after implicit start()"); } } - return e; + return s.currentResource; } @Override @@ -100,23 +111,90 @@ @Override public void start() { - EXECUTOR.compareAndSet(this, null, Schedulers.decorateExecutorService(this, original)); + STATE.compareAndSet(this, null, + SchedulerState.init(Schedulers.decorateExecutorService(this, original))); } @Override + public void init() { + SchedulerState a = this.state; + if (a != null) { + if (a.currentResource == TERMINATED) { + throw new IllegalStateException( + "Initializing a disposed scheduler is not permitted" + ); + } + // return early - scheduler already initialized + return; + } + + if (!STATE.compareAndSet(this, null, + SchedulerState.init(Schedulers.decorateExecutorService(this, original)))) { + if (isDisposed()) { + throw new IllegalStateException( + "Initializing a disposed scheduler is not permitted" + ); + } + } + } + + @Override public boolean isDisposed() { - ScheduledExecutorService e = executor; - return e != null && e.isShutdown(); + SchedulerState current = state; + return current != null && current.currentResource == TERMINATED; } @Override + public boolean await(ScheduledExecutorService resource, long timeout, TimeUnit timeUnit) + throws InterruptedException { + return resource.awaitTermination(timeout, timeUnit); + } + + @Override public void dispose() { - ScheduledExecutorService e = executor; - if (e != null) { - e.shutdownNow(); + SchedulerState previous = state; + + if (previous != null && previous.currentResource == TERMINATED) { + assert previous.initialResource != null; + previous.initialResource.shutdownNow(); + return; } + + SchedulerState terminated = SchedulerState.transition( + previous == null ? null : previous.currentResource, TERMINATED, this); + + STATE.compareAndSet(this, previous, terminated); + + // If unsuccessful - either another thread disposed or restarted - no issue, + // we only care about the one stored in terminated. + if (terminated.initialResource != null) { + terminated.initialResource.shutdownNow(); + } } + @Override + public Mono disposeGracefully() { + return Mono.defer(() -> { + SchedulerState previous = state; + + if (previous != null && previous.currentResource == TERMINATED) { + return previous.onDispose; + } + + SchedulerState terminated = SchedulerState.transition( + previous == null ? null : previous.currentResource, TERMINATED, this); + + STATE.compareAndSet(this, previous, terminated); + + // If unsuccessful - either another thread disposed or restarted - no issue, + // we only care about the one stored in terminated. + if (terminated.initialResource != null) { + terminated.initialResource.shutdown(); + } + return terminated.onDispose; + }); + } + @SuppressWarnings("unchecked") static ScheduledExecutorService convert(ExecutorService executor) { if (executor instanceof ScheduledExecutorService) { @@ -130,9 +208,9 @@ if (key == Attr.TERMINATED || key == Attr.CANCELLED) return isDisposed(); if (key == Attr.NAME) return toString(); - ScheduledExecutorService e = executor; - if (e != null) { - return Schedulers.scanExecutor(e, key); + SchedulerState s = state; + if (s != null) { + return Schedulers.scanExecutor(s.currentResource, key); } return null; } Fisheye: Tag c4ce08dc0aae7d9da822088a3d5710484f6b0402 refers to a dead (removed) revision in file `3rdParty_sources/reactor/reactor/core/scheduler/ElasticScheduler.java'. Fisheye: No comparison available. Pass `N' to diff? Index: 3rdParty_sources/reactor/reactor/core/scheduler/ExecutorScheduler.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/scheduler/ExecutorScheduler.java (.../ExecutorScheduler.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/scheduler/ExecutorScheduler.java (.../ExecutorScheduler.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -57,6 +57,9 @@ throw Exceptions.failWithRejected(); } Objects.requireNonNull(task, "task"); + + task = Schedulers.onSchedule(task); + ExecutorPlainRunnable r = new ExecutorPlainRunnable(task); //RejectedExecutionException are propagated up, but since Executor doesn't from //failing tasks we'll also wrap the execute call in a try catch: @@ -218,21 +221,28 @@ /** * A non-trampolining worker that tracks tasks. */ - static final class ExecutorSchedulerWorker implements Scheduler.Worker, WorkerDelete, Scannable { + static final class ExecutorSchedulerWorker implements Worker, WorkerDelete, Scannable { + private final boolean wrapSchedule; + final Executor executor; - final Disposable.Composite tasks; + final Composite tasks; ExecutorSchedulerWorker(Executor executor) { this.executor = executor; + this.wrapSchedule = !(executor instanceof Scheduler); this.tasks = Disposables.composite(); } @Override public Disposable schedule(Runnable task) { Objects.requireNonNull(task, "task"); + if (wrapSchedule) { + task = Schedulers.onSchedule(task); + } + ExecutorTrackedRunnable r = new ExecutorTrackedRunnable(task, this, true); if (!tasks.add(r)) { throw Exceptions.failWithRejected(); @@ -284,8 +294,10 @@ * A trampolining worker that tracks tasks. */ static final class ExecutorSchedulerTrampolineWorker - implements Scheduler.Worker, WorkerDelete, Runnable, Scannable { + implements Worker, WorkerDelete, Runnable, Scannable { + private final boolean wrapSchedule; + final Executor executor; final Queue queue; @@ -299,6 +311,7 @@ ExecutorSchedulerTrampolineWorker(Executor executor) { this.executor = executor; + this.wrapSchedule = !(executor instanceof Scheduler); this.queue = new ConcurrentLinkedQueue<>(); } @@ -309,6 +322,10 @@ throw Exceptions.failWithRejected(); } + if (wrapSchedule) { + task = Schedulers.onSchedule(task); + } + ExecutorTrackedRunnable r = new ExecutorTrackedRunnable(task, this, false); synchronized (this) { if (terminated) { @@ -405,4 +422,4 @@ } } -} +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/scheduler/ImmediateScheduler.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/scheduler/ImmediateScheduler.java (.../ImmediateScheduler.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/scheduler/ImmediateScheduler.java (.../ImmediateScheduler.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,7 +35,7 @@ static { INSTANCE = new ImmediateScheduler(); - INSTANCE.start(); + INSTANCE.init(); } public static Scheduler instance() { @@ -72,7 +72,7 @@ return new ImmediateSchedulerWorker(); } - static final class ImmediateSchedulerWorker implements Scheduler.Worker, Scannable { + static final class ImmediateSchedulerWorker implements Worker, Scannable { volatile boolean shutdown; @@ -104,4 +104,4 @@ } } -} +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/scheduler/ParallelScheduler.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/scheduler/ParallelScheduler.java (.../ParallelScheduler.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/scheduler/ParallelScheduler.java (.../ParallelScheduler.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,7 @@ import reactor.core.Disposable; import reactor.core.Scannable; +import reactor.core.publisher.Mono; /** * Scheduler that hosts a fixed pool of single-threaded ScheduledExecutorService-based workers @@ -38,26 +39,28 @@ * @author Simon Baslé */ final class ParallelScheduler implements Scheduler, Supplier, + SchedulerState.DisposeAwaiter, Scannable { + static final ScheduledExecutorService TERMINATED; + static final ScheduledExecutorService[] SHUTDOWN = new ScheduledExecutorService[0]; static final AtomicLong COUNTER = new AtomicLong(); - final int n; - - final ThreadFactory factory; - - volatile ScheduledExecutorService[] executors; - static final AtomicReferenceFieldUpdater EXECUTORS = - AtomicReferenceFieldUpdater.newUpdater(ParallelScheduler.class, ScheduledExecutorService[].class, "executors"); - - static final ScheduledExecutorService[] SHUTDOWN = new ScheduledExecutorService[0]; - - static final ScheduledExecutorService TERMINATED; static { TERMINATED = Executors.newSingleThreadScheduledExecutor(); TERMINATED.shutdownNow(); } + final int n; + final ThreadFactory factory; + + volatile SchedulerState state; + @SuppressWarnings("rawtypes") + private static final AtomicReferenceFieldUpdater STATE = + AtomicReferenceFieldUpdater.newUpdater( + ParallelScheduler.class, SchedulerState.class, "state" + ); + int roundRobin; ParallelScheduler(int n, ThreadFactory factory) { @@ -79,75 +82,157 @@ poolExecutor.setRemoveOnCancelPolicy(true); return poolExecutor; } - + @Override public boolean isDisposed() { - return executors == SHUTDOWN; + SchedulerState current = state; + return current != null && current.currentResource == SHUTDOWN; } @Override - public void start() { - ScheduledExecutorService[] b = null; - for (;;) { - ScheduledExecutorService[] a = executors; - if (a != SHUTDOWN && a != null) { - if (b != null) { - for (ScheduledExecutorService exec : b) { - exec.shutdownNow(); - } - } - return; - } + public void init() { + SchedulerState a = this.state; + if (a != null) { + if (a.currentResource == SHUTDOWN) { + throw new IllegalStateException( + "Initializing a disposed scheduler is not permitted" + ); + } + // return early - scheduler already initialized + return; + } - if (b == null) { - b = new ScheduledExecutorService[n]; - for (int i = 0; i < n; i++) { - b[i] = Schedulers.decorateExecutorService(this, this.get()); - } - } - - if (EXECUTORS.compareAndSet(this, a, b)) { - return; - } - } - } + SchedulerState b = + SchedulerState.init(new ScheduledExecutorService[n]); + for (int i = 0; i < n; i++) { + b.currentResource[i] = Schedulers.decorateExecutorService(this, this.get()); + } + + if (!STATE.compareAndSet(this, null, b)) { + for (ScheduledExecutorService exec : b.currentResource) { + exec.shutdownNow(); + } + if (isDisposed()) { + throw new IllegalStateException( + "Initializing a disposed scheduler is not permitted" + ); + } + } + } + + @Override + public void start() { + SchedulerState a = this.state; + + if (a != null && a.currentResource != SHUTDOWN) { + return; + } + + SchedulerState b = + SchedulerState.init(new ScheduledExecutorService[n]); + for (int i = 0; i < n; i++) { + b.currentResource[i] = Schedulers.decorateExecutorService(this, this.get()); + } + + if (STATE.compareAndSet(this, a, b)) { + return; + } + + // someone else shutdown or started successfully, free the resource + for (ScheduledExecutorService exec : b.currentResource) { + exec.shutdownNow(); + } + } + + @Override + public boolean await(ScheduledExecutorService[] resource, long timeout, TimeUnit timeUnit) throws InterruptedException { + for (ScheduledExecutorService executor : resource) { + if (!executor.awaitTermination(timeout, timeUnit)) { + return false; + } + } + return true; + } + @Override - public void dispose() { - ScheduledExecutorService[] a = executors; - if (a != SHUTDOWN) { - a = EXECUTORS.getAndSet(this, SHUTDOWN); - if (a != SHUTDOWN && a != null) { - for (ScheduledExecutorService exec : a) { - exec.shutdownNow(); + public void dispose() { + SchedulerState previous = state; + + if (previous != null && previous.currentResource == SHUTDOWN) { + if (previous.initialResource != null) { + for (ScheduledExecutorService executor : previous.initialResource) { + executor.shutdownNow(); } } + return; } - } - - ScheduledExecutorService pick() { - ScheduledExecutorService[] a = executors; - if (a == null) { - start(); - a = executors; - if (a == null) { - throw new IllegalStateException("executors uninitialized after implicit start()"); + + SchedulerState shutdown = SchedulerState.transition( + previous == null ? null : previous.currentResource, SHUTDOWN, this + ); + + STATE.compareAndSet(this, previous, shutdown); + + // If unsuccessful - either another thread disposed or restarted - no issue, + // we only care about the one stored in shutdown. + if (shutdown.initialResource != null) { + for (ScheduledExecutorService executor : shutdown.initialResource) { + executor.shutdownNow(); } } - if (a != SHUTDOWN) { - // ignoring the race condition here, its already random who gets which executor - int idx = roundRobin; - if (idx == n) { - idx = 0; - roundRobin = 1; - } else { - roundRobin = idx + 1; + } + + @Override + public Mono disposeGracefully() { + return Mono.defer(() -> { + SchedulerState previous = state; + + if (previous != null && previous.currentResource == SHUTDOWN) { + return previous.onDispose; } - return a[idx]; - } - return TERMINATED; - } + SchedulerState shutdown = SchedulerState.transition( + previous == null ? null : previous.currentResource, SHUTDOWN, this + ); + + STATE.compareAndSet(this, previous, shutdown); + + // If unsuccessful - either another thread disposed or restarted - no issue, + // we only care about the one stored in shutdown. + if (shutdown.initialResource != null) { + for (ScheduledExecutorService executor : shutdown.initialResource) { + executor.shutdown(); + } + } + return shutdown.onDispose; + }); + } + + ScheduledExecutorService pick() { + SchedulerState a = state; + if (a == null) { + init(); + a = state; + if (a == null) { + throw new IllegalStateException("executors uninitialized after implicit init()"); + } + } + if (a.currentResource != SHUTDOWN) { + // ignoring the race condition here, its already random who gets which executor + int idx = roundRobin; + if (idx == n) { + idx = 0; + roundRobin = 1; + } + else { + roundRobin = idx + 1; + } + return a.currentResource[idx]; + } + return TERMINATED; + } + @Override public Disposable schedule(Runnable task) { return Schedulers.directSchedule(pick(), task, null, 0L, TimeUnit.MILLISECONDS); @@ -192,7 +277,7 @@ @Override public Stream inners() { - return Stream.of(executors) + return Stream.of(state.currentResource) .map(exec -> key -> Schedulers.scanExecutor(exec, key)); } Index: 3rdParty_sources/reactor/reactor/core/scheduler/ReactorBlockHoundIntegration.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/scheduler/ReactorBlockHoundIntegration.java (.../ReactorBlockHoundIntegration.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/scheduler/ReactorBlockHoundIntegration.java (.../ReactorBlockHoundIntegration.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2019-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import reactor.blockhound.BlockHound; import reactor.blockhound.integration.BlockHoundIntegration; +import java.util.concurrent.FutureTask; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor; @@ -38,10 +39,18 @@ builder.allowBlockingCallsInside(ScheduledThreadPoolExecutor.class.getName() + "$DelayedWorkQueue", "offer"); builder.allowBlockingCallsInside(ScheduledThreadPoolExecutor.class.getName() + "$DelayedWorkQueue", "take"); + builder.allowBlockingCallsInside(BoundedElasticScheduler.class.getName() + "$BoundedScheduledExecutorService", "ensureQueueCapacity"); // Calls ScheduledFutureTask#cancel that may short park in DelayedWorkQueue#remove for getting a lock builder.allowBlockingCallsInside(SchedulerTask.class.getName(), "dispose"); + builder.allowBlockingCallsInside(WorkerTask.class.getName(), "dispose"); builder.allowBlockingCallsInside(ThreadPoolExecutor.class.getName(), "processWorkerExit"); + + // Most allowances are from the schedulers package but this one is from the publisher package. + // For now, let's not add a separate integration, but rather let's define the class name manually + // ContextRegistry reads files as part of the Service Loader aspect. If class is initialized in a non-blocking thread, BlockHound would complain + builder.allowBlockingCallsInside("reactor.core.publisher.ContextPropagation", ""); + builder.allowBlockingCallsInside(FutureTask.class.getName(),"handlePossibleCancellationInterrupt"); } } Index: 3rdParty_sources/reactor/reactor/core/scheduler/Scheduler.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/scheduler/Scheduler.java (.../Scheduler.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/scheduler/Scheduler.java (.../Scheduler.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,15 +16,16 @@ package reactor.core.scheduler; -import java.util.concurrent.Executor; +import reactor.core.Disposable; +import reactor.core.Exceptions; +import reactor.core.publisher.Mono; + +import java.time.Duration; import java.util.concurrent.ExecutorService; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import reactor.core.Disposable; -import reactor.core.Exceptions; - /** * Provides an abstract asynchronous boundary to operators. *

                                @@ -128,28 +129,66 @@ * Instructs this Scheduler to release all resources and reject * any new tasks to be executed. * - *

                                The operation is thread-safe but one should avoid using - * start() and dispose() concurrently as it would non-deterministically - * leave the Scheduler in either active or inactive state. + *

                                The operation is thread-safe. * *

                                The Scheduler may choose to ignore this instruction. - * + *

                                When used in combination with {@link #disposeGracefully()} + * there are no guarantees that all resources will be forcefully shutdown. + * When a graceful disposal has started, the references to the underlying + * {@link java.util.concurrent.Executor}s might have already been lost. */ default void dispose() { } /** + * Lazy variant of {@link #dispose()} that also allows for graceful cleanup + * of underlying resources. + *

                                It is advised to apply a {@link Mono#timeout(Duration)} operator to the + * resulting {@link Mono}. + *

                                The returned {@link Mono} can be {@link Mono#retry(long) retried} in case of + * {@link java.util.concurrent.TimeoutException timeout errors}. It can also be + * followed by a call to {@link #dispose()} to issue a forceful shutdown of + * underlying resources. + * + * @return {@link Mono} which upon subscription initiates the graceful dispose + * procedure. If the disposal is successful, the returned {@link Mono} completes + * without an error. + */ + default Mono disposeGracefully() { + return Mono.fromRunnable(this::dispose); + } + + /** * Instructs this Scheduler to prepare itself for running tasks * directly or through its Workers. * *

                                The operation is thread-safe but one should avoid using * start() and dispose() concurrently as it would non-deterministically * leave the Scheduler in either active or inactive state. + * + * @deprecated Use {@link #init()} instead. The use of this method is discouraged. + * Some implementations allowed restarting a Scheduler, while others did not. One + * of the issues with restarting is that checking + * {@link #isDisposed() the disposed state} is unreliable in concurrent scenarios. + * @see #init() */ + @Deprecated default void start() { } /** + * Instructs this Scheduler to prepare itself for running tasks + * directly or through its {@link Worker}s. + * + *

                                Implementations are encouraged to throw an exception if this method is called + * after the scheduler has been disposed via {@link #dispose()} + * or {@link #disposeGracefully()}. + */ + default void init() { + start(); + } + + /** * A worker representing an asynchronous boundary that executes tasks. * * @author Stephane Maldini Index: 3rdParty_sources/reactor/reactor/core/scheduler/SchedulerMetricDecorator.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/scheduler/SchedulerMetricDecorator.java (.../SchedulerMetricDecorator.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/scheduler/SchedulerMetricDecorator.java (.../SchedulerMetricDecorator.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2018-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,7 @@ import java.util.function.BiFunction; import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.core.instrument.Tags; +import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.binder.jvm.ExecutorServiceMetrics; import io.micrometer.core.instrument.search.Search; @@ -34,6 +34,7 @@ import reactor.core.Scannable.Attr; import reactor.util.Metrics; +@Deprecated final class SchedulerMetricDecorator implements BiFunction, Disposable { @@ -73,7 +74,7 @@ executorDifferentiator.computeIfAbsent(scheduler, key -> new AtomicInteger(0)) .getAndIncrement(); - Tags tags = Tags.of(TAG_SCHEDULER_ID, schedulerId); + Tag[] tags = new Tag[] { Tag.of(TAG_SCHEDULER_ID, schedulerId) }; /* Design note: we assume that a given Scheduler won't apply the decorator twice to the Index: 3rdParty_sources/reactor/reactor/core/scheduler/SchedulerState.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/scheduler/SchedulerState.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/scheduler/SchedulerState.java (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2022 VMware Inc. or its affiliates, All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reactor.core.scheduler; + +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.FluxSink; +import reactor.core.publisher.Mono; +import reactor.util.annotation.Nullable; + +import static reactor.core.scheduler.SchedulerState.DisposeAwaiterRunnable.awaitInPool; + +final class SchedulerState { + + @Nullable + final T initialResource; + final T currentResource; + final Mono onDispose; + + private SchedulerState(@Nullable T initialResource, T currentResource, Mono onDispose) { + this.initialResource = initialResource; + this.currentResource = currentResource; + this.onDispose = onDispose; + } + + static SchedulerState init(final T resource) { + return new SchedulerState<>(resource, resource, Mono.empty()); + } + + static SchedulerState transition(@Nullable T initial, T next, DisposeAwaiter awaiter) { + return new SchedulerState( + initial, + next, + initial == null ? Mono.empty() : + Flux.create(sink -> awaitInPool(awaiter, initial, sink, 100)) + .replay() + .refCount() + .next()); + } + + interface DisposeAwaiter { + + boolean await(T resource, long timeout, TimeUnit timeUnit) throws InterruptedException; + } + + static class DisposeAwaiterRunnable implements Runnable { + + static final ScheduledExecutorService TRANSITION_AWAIT_POOL; + + static { + ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(0); + executor.setKeepAliveTime(10, TimeUnit.SECONDS); + executor.allowCoreThreadTimeOut(true); + executor.setMaximumPoolSize(Schedulers.DEFAULT_POOL_SIZE); + TRANSITION_AWAIT_POOL = executor; + } + + private final DisposeAwaiter awaiter; + private final T initial; + private final int awaitMs; + private final FluxSink sink; + + volatile boolean cancelled; + + static void awaitInPool(DisposeAwaiter awaiter, R initial, FluxSink sink, int awaitMs) { + DisposeAwaiterRunnable poller = new DisposeAwaiterRunnable<>(awaiter, initial, sink, awaitMs); + TRANSITION_AWAIT_POOL.submit(poller); + } + + DisposeAwaiterRunnable(DisposeAwaiter awaiter, T initial, FluxSink sink, int awaitMs) { + this.awaiter = awaiter; + this.initial = initial; + this.sink = sink; + this.awaitMs = awaitMs; + //can only call onCancel once so we rely on DisposeAwaiterRunnable#cancel + sink.onCancel(this::cancel); + } + + void cancel() { + cancelled = true; + //we don't really care about the future. next round we'll abandon the task + } + + @Override + public void run() { + if (cancelled) { + return; + } + try { + if (awaiter.await(initial, awaitMs, TimeUnit.MILLISECONDS)) { + sink.complete(); + } + else { + if (cancelled) { + return; + } + // trampoline + TRANSITION_AWAIT_POOL.submit(this); + } + } + catch (InterruptedException e) { + //NO-OP + } + } + } +} Index: 3rdParty_sources/reactor/reactor/core/scheduler/Schedulers.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/scheduler/Schedulers.java (.../Schedulers.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/scheduler/Schedulers.java (.../Schedulers.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package reactor.core.scheduler; +import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; import java.util.Objects; @@ -54,15 +55,14 @@ *

                                  *
                                • {@link #parallel()}: Optimized for fast {@link Runnable} non-blocking executions
                                • *
                                • {@link #single}: Optimized for low-latency {@link Runnable} one-off executions
                                • - *
                                • {@link #elastic()}: Optimized for longer executions, an alternative for blocking tasks where the number of active tasks (and threads) can grow indefinitely
                                • *
                                • {@link #boundedElastic()}: Optimized for longer executions, an alternative for blocking tasks where the number of active tasks (and threads) is capped
                                • *
                                • {@link #immediate}: to immediately run submitted {@link Runnable} instead of scheduling them (somewhat of a no-op or "null object" {@link Scheduler})
                                • *
                                • {@link #fromExecutorService(ExecutorService)} to create new instances around {@link java.util.concurrent.Executors}
                                • *
                                *

                                * Factories prefixed with {@code new} (eg. {@link #newBoundedElastic(int, int, String)} return a new instance of their flavor of {@link Scheduler}, * while other factories like {@link #boundedElastic()} return a shared instance - which is the one used by operators requiring that flavor as their default Scheduler. - * All instances are returned in a {@link Scheduler#start() started} state. + * All instances are returned in a {@link Scheduler#init() initialized} state. * * @author Stephane Maldini */ @@ -104,9 +104,6 @@ .map(Integer::parseInt) .orElse(100000); - @Nullable - static volatile BiConsumer onHandleErrorHook; - /** * Create a {@link Scheduler} which uses a backing {@link Executor} to schedule * Runnables for async operators. @@ -142,7 +139,7 @@ return fromExecutorService((ExecutorService) executor); } final ExecutorScheduler scheduler = new ExecutorScheduler(executor, trampoline); - scheduler.start(); + scheduler.init(); return scheduler; } @@ -172,34 +169,13 @@ */ public static Scheduler fromExecutorService(ExecutorService executorService, String executorName) { final DelegateServiceScheduler scheduler = new DelegateServiceScheduler(executorName, executorService); - scheduler.start(); + scheduler.init(); return scheduler; } /** - * {@link Scheduler} that dynamically creates ExecutorService-based Workers and caches - * the thread pools, reusing them once the Workers have been shut down. - *

                                - * The maximum number of created thread pools is unbounded. - *

                                - * The default time-to-live for unused thread pools is 60 seconds, use the appropriate - * factory to set a different value. - *

                                - * This scheduler is not restartable. - * - * @return default instance of a {@link Scheduler} that dynamically creates ExecutorService-based - * Workers and caches the threads, reusing them once the Workers have been shut - * down - * @deprecated use {@link #boundedElastic()}, to be removed in 3.5.0 - */ - @Deprecated - public static Scheduler elastic() { - return cache(CACHED_ELASTIC, ELASTIC, ELASTIC_SUPPLIER); - } - - /** - * {@link Scheduler} that dynamically creates a bounded number of ExecutorService-based - * Workers, reusing them once the Workers have been shut down. The underlying daemon + * The common boundedElastic instance, a {@link Scheduler} that dynamically creates a bounded number of + * ExecutorService-based Workers, reusing them once the Workers have been shut down. The underlying daemon * threads can be evicted if idle for more than {@link BoundedElasticScheduler#DEFAULT_TTL_SECONDS 60} seconds. *

                                * The maximum number of created threads is bounded by a {@code cap} (by default @@ -209,7 +185,7 @@ * {@link #DEFAULT_BOUNDED_ELASTIC_QUEUESIZE}). Past that point, a {@link RejectedExecutionException} * is thrown. *

                                - * By order of preference, threads backing a new {@link reactor.core.scheduler.Scheduler.Worker} are + * By order of preference, threads backing a new {@link Scheduler.Worker} are * picked from the idle pool, created anew or reused from the busy pool. In the later case, a best effort * attempt at picking the thread backing the least amount of workers is made. *

                                @@ -218,20 +194,34 @@ * The picking of the backing thread is also done once and for all at worker creation, so * tasks could be delayed due to two workers sharing the same backing thread and submitting long-running tasks, * despite another backing thread becoming idle in the meantime. + *

                                + * Only one instance of this common scheduler will be created on the first call and is cached. The same instance + * is returned on subsequent calls until it is disposed. + *

                                + * One cannot directly {@link Scheduler#dispose() dispose} the common instances, as they are cached and shared + * between callers. They can however be all {@link #shutdownNow() shut down} together, or replaced by a + * {@link #setFactory(Factory) change in Factory}. * - * @return a new {@link Scheduler} that dynamically create workers with an upper bound to - * the number of backing threads and after that on the number of enqueued tasks, - * that reuses threads and evict idle ones + * @return the common boundedElastic instance, a {@link Scheduler} that dynamically creates workers with + * an upper bound to the number of backing threads and after that on the number of enqueued tasks, that reuses + * threads and evict idle ones */ public static Scheduler boundedElastic() { return cache(CACHED_BOUNDED_ELASTIC, BOUNDED_ELASTIC, BOUNDED_ELASTIC_SUPPLIER); } /** - * {@link Scheduler} that hosts a fixed pool of single-threaded ExecutorService-based - * workers and is suited for parallel work. + * The common parallel instance, a {@link Scheduler} that hosts a fixed pool of single-threaded + * ExecutorService-based workers and is suited for parallel work. + *

                                + * Only one instance of this common scheduler will be created on the first call and is cached. The same instance + * is returned on subsequent calls until it is disposed. + *

                                + * One cannot directly {@link Scheduler#dispose() dispose} the common instances, as they are cached and shared + * between callers. They can however be all {@link #shutdownNow() shut down} together, or replaced by a + * {@link #setFactory(Factory) change in Factory}. * - * @return default instance of a {@link Scheduler} that hosts a fixed pool of single-threaded + * @return the common parallel instance, a {@link Scheduler} that hosts a fixed pool of single-threaded * ExecutorService-based workers and is suited for parallel work */ public static Scheduler parallel() { @@ -253,99 +243,6 @@ } /** - * {@link Scheduler} that dynamically creates ExecutorService-based Workers and caches - * the thread pools, reusing them once the Workers have been shut down. - *

                                - * The maximum number of created thread pools is unbounded. - *

                                - * The default time-to-live for unused thread pools is 60 seconds, use the appropriate - * factory to set a different value. - *

                                - * This scheduler is not restartable. - * - * @param name Thread prefix - * - * @return a new {@link Scheduler} that dynamically creates ExecutorService-based - * Workers and caches the thread pools, reusing them once the Workers have been shut - * down - * @deprecated use {@link #newBoundedElastic(int, int, String)}, to be removed in 3.5.0 - */ - @Deprecated - public static Scheduler newElastic(String name) { - return newElastic(name, ElasticScheduler.DEFAULT_TTL_SECONDS); - } - - /** - * {@link Scheduler} that dynamically creates ExecutorService-based Workers and caches - * the thread pools, reusing them once the Workers have been shut down. - *

                                - * The maximum number of created thread pools is unbounded. - *

                                - * This scheduler is not restartable. - * - * @param name Thread prefix - * @param ttlSeconds Time-to-live for an idle {@link reactor.core.scheduler.Scheduler.Worker} - * - * @return a new {@link Scheduler} that dynamically creates ExecutorService-based - * Workers and caches the thread pools, reusing them once the Workers have been shut - * down - * @deprecated use {@link #newBoundedElastic(int, int, String, int)}, to be removed in 3.5.0 - */ - @Deprecated - public static Scheduler newElastic(String name, int ttlSeconds) { - return newElastic(name, ttlSeconds, false); - } - - /** - * {@link Scheduler} that dynamically creates ExecutorService-based Workers and caches - * the thread pools, reusing them once the Workers have been shut down. - *

                                - * The maximum number of created thread pools is unbounded. - *

                                - * This scheduler is not restartable. - * - * @param name Thread prefix - * @param ttlSeconds Time-to-live for an idle {@link reactor.core.scheduler.Scheduler.Worker} - * @param daemon false if the {@link Scheduler} requires an explicit {@link - * Scheduler#dispose()} to exit the VM. - * - * @return a new {@link Scheduler} that dynamically creates ExecutorService-based - * Workers and caches the thread pools, reusing them once the Workers have been shut - * down - * @deprecated use {@link #newBoundedElastic(int, int, String, int, boolean)}, to be removed in 3.5.0 - */ - @Deprecated - public static Scheduler newElastic(String name, int ttlSeconds, boolean daemon) { - return newElastic(ttlSeconds, - new ReactorThreadFactory(name, ElasticScheduler.COUNTER, daemon, false, - Schedulers::defaultUncaughtException)); - } - - /** - * {@link Scheduler} that dynamically creates ExecutorService-based Workers and caches - * the thread pools, reusing them once the Workers have been shut down. - *

                                - * The maximum number of created thread pools is unbounded. - *

                                - * This scheduler is not restartable. - * - * @param ttlSeconds Time-to-live for an idle {@link reactor.core.scheduler.Scheduler.Worker} - * @param threadFactory a {@link ThreadFactory} to use each thread initialization - * - * @return a new {@link Scheduler} that dynamically creates ExecutorService-based - * Workers and caches the thread pools, reusing them once the Workers have been shut - * down - * @deprecated use {@link #newBoundedElastic(int, int, ThreadFactory, int)}, to be removed in 3.5.0 - */ - @Deprecated - public static Scheduler newElastic(int ttlSeconds, ThreadFactory threadFactory) { - final Scheduler fromFactory = factory.newElastic(ttlSeconds, threadFactory); - fromFactory.start(); - return fromFactory; - } - - - /** * {@link Scheduler} that dynamically creates a bounded number of ExecutorService-based * Workers, reusing them once the Workers have been shut down. The underlying (user) * threads can be evicted if idle for more than {@link BoundedElasticScheduler#DEFAULT_TTL_SECONDS 60} seconds. @@ -355,7 +252,7 @@ * backing threads is bounded by the provided {@code queuedTaskCap}. Past that point, * a {@link RejectedExecutionException} is thrown. *

                                - * By order of preference, threads backing a new {@link reactor.core.scheduler.Scheduler.Worker} are + * By order of preference, threads backing a new {@link Scheduler.Worker} are * picked from the idle pool, created anew or reused from the busy pool. In the later case, a best effort * attempt at picking the thread backing the least amount of workers is made. *

                                @@ -365,14 +262,14 @@ * tasks could be delayed due to two workers sharing the same backing thread and submitting long-running tasks, * despite another backing thread becoming idle in the meantime. *

                                - * This scheduler is restartable. Backing threads are user threads, so they will prevent the JVM + * Threads backing this scheduler are user threads, so they will prevent the JVM * from exiting until their worker has been disposed AND they've been evicted by TTL, or the whole * scheduler has been {@link Scheduler#dispose() disposed}. * * @param threadCap maximum number of underlying threads to create * @param queuedTaskCap maximum number of tasks to enqueue when no more threads can be created. Can be {@link Integer#MAX_VALUE} for unbounded enqueueing. * @param name Thread prefix - * @return a new {@link Scheduler} that dynamically create workers with an upper bound to + * @return a new {@link Scheduler} that dynamically creates workers with an upper bound to * the number of backing threads and after that on the number of enqueued tasks, * that reuses threads and evict idle ones */ @@ -390,7 +287,7 @@ * backing threads is bounded by the provided {@code queuedTaskCap}. Past that point, * a {@link RejectedExecutionException} is thrown. *

                                - * By order of preference, threads backing a new {@link reactor.core.scheduler.Scheduler.Worker} are + * By order of preference, threads backing a new {@link Scheduler.Worker} are * picked from the idle pool, created anew or reused from the busy pool. In the later case, a best effort * attempt at picking the thread backing the least amount of workers is made. *

                                @@ -400,15 +297,15 @@ * tasks could be delayed due to two workers sharing the same backing thread and submitting long-running tasks, * despite another backing thread becoming idle in the meantime. *

                                - * This scheduler is restartable. Backing threads are user threads, so they will prevent the JVM + * Threads backing this scheduler are user threads, so they will prevent the JVM * from exiting until their worker has been disposed AND they've been evicted by TTL, or the whole * scheduler has been {@link Scheduler#dispose() disposed}. * * @param threadCap maximum number of underlying threads to create * @param queuedTaskCap maximum number of tasks to enqueue when no more threads can be created. Can be {@link Integer#MAX_VALUE} for unbounded enqueueing. * @param name Thread prefix - * @param ttlSeconds Time-to-live for an idle {@link reactor.core.scheduler.Scheduler.Worker} - * @return a new {@link Scheduler} that dynamically create workers with an upper bound to + * @param ttlSeconds Time-to-live for an idle {@link Scheduler.Worker} + * @return a new {@link Scheduler} that dynamically creates workers with an upper bound to * the number of backing threads and after that on the number of enqueued tasks, * that reuses threads and evict idle ones */ @@ -426,7 +323,7 @@ * backing threads is bounded by the provided {@code queuedTaskCap}. Past that point, * a {@link RejectedExecutionException} is thrown. *

                                - * By order of preference, threads backing a new {@link reactor.core.scheduler.Scheduler.Worker} are + * By order of preference, threads backing a new {@link Scheduler.Worker} are * picked from the idle pool, created anew or reused from the busy pool. In the later case, a best effort * attempt at picking the thread backing the least amount of workers is made. *

                                @@ -436,23 +333,23 @@ * tasks could be delayed due to two workers sharing the same backing thread and submitting long-running tasks, * despite another backing thread becoming idle in the meantime. *

                                - * This scheduler is restartable. Depending on the {@code daemon} parameter, backing threads can be + * Depending on the {@code daemon} parameter, threads backing this scheduler can be * user threads or daemon threads. Note that user threads will prevent the JVM from exiting until their * worker has been disposed AND they've been evicted by TTL, or the whole scheduler has been * {@link Scheduler#dispose() disposed}. * * @param threadCap maximum number of underlying threads to create * @param queuedTaskCap maximum number of tasks to enqueue when no more threads can be created. Can be {@link Integer#MAX_VALUE} for unbounded enqueueing. * @param name Thread prefix - * @param ttlSeconds Time-to-live for an idle {@link reactor.core.scheduler.Scheduler.Worker} + * @param ttlSeconds Time-to-live for an idle {@link Scheduler.Worker} * @param daemon are backing threads {@link Thread#setDaemon(boolean) daemon threads} - * @return a new {@link Scheduler} that dynamically create workers with an upper bound to + * @return a new {@link Scheduler} that dynamically creates workers with an upper bound to * the number of backing threads and after that on the number of enqueued tasks, * that reuses threads and evict idle ones */ public static Scheduler newBoundedElastic(int threadCap, int queuedTaskCap, String name, int ttlSeconds, boolean daemon) { return newBoundedElastic(threadCap, queuedTaskCap, - new ReactorThreadFactory(name, ElasticScheduler.COUNTER, daemon, false, + new ReactorThreadFactory(name, BoundedElasticScheduler.COUNTER, daemon, false, Schedulers::defaultUncaughtException), ttlSeconds); } @@ -467,7 +364,7 @@ * backing threads is bounded by the provided {@code queuedTaskCap}. Past that point, * a {@link RejectedExecutionException} is thrown. *

                                - * By order of preference, threads backing a new {@link reactor.core.scheduler.Scheduler.Worker} are + * By order of preference, threads backing a new {@link Scheduler.Worker} are * picked from the idle pool, created anew or reused from the busy pool. In the later case, a best effort * attempt at picking the thread backing the least amount of workers is made. *

                                @@ -477,16 +374,16 @@ * tasks could be delayed due to two workers sharing the same backing thread and submitting long-running tasks, * despite another backing thread becoming idle in the meantime. *

                                - * This scheduler is restartable. Backing threads are created by the provided {@link ThreadFactory}, + * Threads backing this scheduler are created by the provided {@link ThreadFactory}, * which can decide whether to create user threads or daemon threads. Note that user threads * will prevent the JVM from exiting until their worker has been disposed AND they've been evicted by TTL, * or the whole scheduler has been {@link Scheduler#dispose() disposed}. * * @param threadCap maximum number of underlying threads to create * @param queuedTaskCap maximum number of tasks to enqueue when no more threads can be created. Can be {@link Integer#MAX_VALUE} for unbounded enqueueing. * @param threadFactory a {@link ThreadFactory} to use each thread initialization - * @param ttlSeconds Time-to-live for an idle {@link reactor.core.scheduler.Scheduler.Worker} - * @return a new {@link Scheduler} that dynamically create workers with an upper bound to + * @param ttlSeconds Time-to-live for an idle {@link Scheduler.Worker} + * @return a new {@link Scheduler} that dynamically creates workers with an upper bound to * the number of backing threads and after that on the number of enqueued tasks, * that reuses threads and evict idle ones */ @@ -495,7 +392,7 @@ queuedTaskCap, threadFactory, ttlSeconds); - fromFactory.start(); + fromFactory.init(); return fromFactory; } @@ -560,14 +457,13 @@ */ public static Scheduler newParallel(int parallelism, ThreadFactory threadFactory) { final Scheduler fromFactory = factory.newParallel(parallelism, threadFactory); - fromFactory.start(); + fromFactory.init(); return fromFactory; } /** - * {@link Scheduler} that hosts a single-threaded ExecutorService-based worker and is - * suited for parallel work. This type of {@link Scheduler} detects and rejects usage - * * of blocking Reactor APIs. + * {@link Scheduler} that hosts a single-threaded ExecutorService-based worker. This type of {@link Scheduler} + * detects and rejects usage of blocking Reactor APIs. * * @param name Component and thread name prefix * @@ -579,9 +475,8 @@ } /** - * {@link Scheduler} that hosts a single-threaded ExecutorService-based worker and is - * suited for parallel work. This type of {@link Scheduler} detects and rejects usage - * of blocking Reactor APIs. + * {@link Scheduler} that hosts a single-threaded ExecutorService-based worker. This type of {@link Scheduler} + * detects and rejects usage of blocking Reactor APIs. * * @param name Component and thread name prefix * @param daemon false if the {@link Scheduler} requires an explicit {@link @@ -596,8 +491,7 @@ } /** - * {@link Scheduler} that hosts a single-threaded ExecutorService-based worker and is - * suited for parallel work. + * {@link Scheduler} that hosts a single-threaded ExecutorService-based worker. * * @param threadFactory a {@link ThreadFactory} to use for the unique thread of the * {@link Scheduler} @@ -607,26 +501,76 @@ */ public static Scheduler newSingle(ThreadFactory threadFactory) { final Scheduler fromFactory = factory.newSingle(threadFactory); - fromFactory.start(); + fromFactory.init(); return fromFactory; } /** - * Define a hook that is executed when a {@link Scheduler} has + * Define a hook anonymous part that is executed alongside keyed parts when a {@link Scheduler} has * {@link #handleError(Throwable) handled an error}. Note that it is executed after * the error has been passed to the thread uncaughtErrorHandler, which is not the * case when a fatal error occurs (see {@link Exceptions#throwIfJvmFatal(Throwable)}). + *

                                + * This variant uses an internal private key, which allows the method to be additive with + * {@link #onHandleError(String, BiConsumer)}. Prefer adding and removing handler parts + * for keys that you own via {@link #onHandleError(String, BiConsumer)} nonetheless. * - * @param c the new hook to set. + * @param subHook the new {@link BiConsumer} to set as the hook's anonymous part. + * @see #onHandleError(String, BiConsumer) */ - public static void onHandleError(BiConsumer c) { + public static void onHandleError(BiConsumer subHook) { + Objects.requireNonNull(subHook, "onHandleError"); if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Hooking new default: onHandleError"); + LOGGER.debug("Hooking onHandleError anonymous part"); } - onHandleErrorHook = Objects.requireNonNull(c, "onHandleError"); + synchronized (LOGGER) { + onHandleErrorHooks.put(Schedulers.class.getName() + ".ON_HANDLE_ERROR_ANONYMOUS_PART", (BiConsumer) subHook); + onHandleErrorHook = createOrAppendHandleError(onHandleErrorHooks.values()); + } } /** + * Define a keyed hook part that is executed alongside other parts when a {@link Scheduler} has + * {@link #handleError(Throwable) handled an error}. Note that it is executed after + * the error has been passed to the thread uncaughtErrorHandler, which is not the + * case when a fatal error occurs (see {@link Exceptions#throwIfJvmFatal(Throwable)}). + *

                                + * Calling this method twice with the same key replaces the old hook part + * of the same key. Calling this method twice with two different keys is otherwise additive. + * Note that {@link #onHandleError(BiConsumer)} also defines an anonymous part which + * effectively uses a private internal key, making it also additive with this method. + * + * @param key the {@link String} key identifying the hook part to set/replace. + * @param subHook the new hook part to set for the given key. + */ + @SuppressWarnings("unchecked") + public static void onHandleError(String key, BiConsumer subHook) { + Objects.requireNonNull(key, "key"); + Objects.requireNonNull(subHook, "onHandleError"); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Hooking onHandleError part with key {}", key); + } + synchronized (LOGGER) { + onHandleErrorHooks.put(key, (BiConsumer) subHook); + onHandleErrorHook = createOrAppendHandleError(onHandleErrorHooks.values()); + } + } + + @Nullable + private static BiConsumer createOrAppendHandleError(Collection> subHooks) { + BiConsumer composite = null; + for (BiConsumer value : subHooks) { + if (composite != null) { + composite = composite.andThen(value); + } + else { + composite = value; + } + } + return composite; + } + + /** * Check if calling a Reactor blocking API in the current {@link Thread} is forbidden * or not, by checking if the thread implements {@link NonBlocking} (in which case it is * forbidden and this method returns {@code true}). @@ -655,13 +599,15 @@ * *

                                * The {@link MeterRegistry} used by reactor can be configured via - * {@link reactor.util.Metrics.MicrometerConfiguration#useRegistry(MeterRegistry)} + * {@link Metrics.MicrometerConfiguration#useRegistry(MeterRegistry)} * prior to using this method, the default being * {@link io.micrometer.core.instrument.Metrics#globalRegistry}. *

                                * * @implNote Note that this is added as a decorator via Schedulers when enabling metrics for schedulers, which doesn't change the Factory. + * @deprecated prefer using Micrometer#timedScheduler from the reactor-core-micrometer module. To be removed at the earliest in 3.6.0. */ + @Deprecated public static void enableMetrics() { if (Metrics.isInstrumentationAvailable()) { addExecutorServiceDecorator(SchedulerMetricDecorator.METRICS_DECORATOR_KEY, new SchedulerMetricDecorator()); @@ -671,7 +617,10 @@ /** * If {@link #enableMetrics()} has been previously called, removes the decorator. * No-op if {@link #enableMetrics()} hasn't been called. + * + * @deprecated prefer using Micrometer#timedScheduler from the reactor-core-micrometer module. To be removed at the earliest in 3.6.0. */ + @Deprecated public static void disableMetrics() { removeExecutorServiceDecorator(SchedulerMetricDecorator.METRICS_DECORATOR_KEY); } @@ -698,7 +647,6 @@ //nulling out CACHED references ensures that the schedulers won't be disposed //when setting the newFactory via setFactory Snapshot snapshot = new Snapshot( - CACHED_ELASTIC.getAndSet(null), CACHED_BOUNDED_ELASTIC.getAndSet(null), CACHED_PARALLEL.getAndSet(null), CACHED_SINGLE.getAndSet(null), @@ -708,7 +656,7 @@ } /** - * Replace the current Factory and shared Schedulers with the ones saved in a + * Replace the current Factory and shared Schedulers with the ones saved in a * previously {@link #setFactoryWithSnapshot(Factory) captured} snapshot. *

                                * Passing {@code null} re-applies the default factory. @@ -720,7 +668,6 @@ } //Restore the atomic references first, so that concurrent calls to Schedulers either //get a soon-to-be-shutdown instance or the restored instance - CachedScheduler oldElastic = CACHED_ELASTIC.getAndSet(snapshot.oldElasticScheduler); CachedScheduler oldBoundedElastic = CACHED_BOUNDED_ELASTIC.getAndSet(snapshot.oldBoundedElasticScheduler); CachedScheduler oldParallel = CACHED_PARALLEL.getAndSet(snapshot.oldParallelScheduler); CachedScheduler oldSingle = CACHED_SINGLE.getAndSet(snapshot.oldSingleScheduler); @@ -731,23 +678,46 @@ factory = snapshot.oldFactory; //Shutdown the old CachedSchedulers, if any - if (oldElastic != null) oldElastic._dispose(); if (oldBoundedElastic != null) oldBoundedElastic._dispose(); if (oldParallel != null) oldParallel._dispose(); if (oldSingle != null) oldSingle._dispose(); } /** - * Reset the {@link #onHandleError(BiConsumer)} hook to the default no-op behavior. + * Reset the {@link #onHandleError(BiConsumer)} hook to the default no-op behavior, erasing + * all sub-hooks that might have individually added via {@link #onHandleError(String, BiConsumer)} + * or the whole hook set via {@link #onHandleError(BiConsumer)}. + * + * @see #resetOnHandleError(String) */ public static void resetOnHandleError() { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Reset to factory defaults: onHandleError"); } - onHandleErrorHook = null; + synchronized (LOGGER) { + onHandleErrorHooks.clear(); + onHandleErrorHook = null; + } } /** + * Reset a specific onHandleError hook part keyed to the provided {@link String}, + * removing that sub-hook if it has previously been defined via {@link #onHandleError(String, BiConsumer)}. + */ + public static void resetOnHandleError(String key) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Remove onHandleError sub-hook {}", key); + } + synchronized (LOGGER) { + //avoid resetting monolithic hook if no keyed hook has been set + //also avoid resetting anything if the key is unknown + if (onHandleErrorHooks.remove(key) != null) { + onHandleErrorHook = createOrAppendHandleError(onHandleErrorHooks.values()); + } + } + } + + /** * Replace {@link Schedulers} factories ({@link #newParallel(String) newParallel}, * {@link #newSingle(String) newSingle} and {@link #newBoundedElastic(int, int, String) newBoundedElastic}). * Also shutdown Schedulers from the cached factories (like {@link #single()}) in order to @@ -947,39 +917,44 @@ * Clear any cached {@link Scheduler} and call dispose on them. */ public static void shutdownNow() { - CachedScheduler oldElastic = CACHED_ELASTIC.getAndSet(null); CachedScheduler oldBoundedElastic = CACHED_BOUNDED_ELASTIC.getAndSet(null); CachedScheduler oldParallel = CACHED_PARALLEL.getAndSet(null); CachedScheduler oldSingle = CACHED_SINGLE.getAndSet(null); - if (oldElastic != null) oldElastic._dispose(); if (oldBoundedElastic != null) oldBoundedElastic._dispose(); if (oldParallel != null) oldParallel._dispose(); if (oldSingle != null) oldSingle._dispose(); } /** - * {@link Scheduler} that hosts a single-threaded ExecutorService-based worker and is - * suited for parallel work. Will cache the returned schedulers for subsequent calls until dispose. + * The common single instance, a {@link Scheduler} that hosts a single-threaded ExecutorService-based + * worker. + *

                                + * Only one instance of this common scheduler will be created on the first call and is cached. The same instance + * is returned on subsequent calls until it is disposed. + *

                                + * One cannot directly {@link Scheduler#dispose() dispose} the common instances, as they are cached and shared + * between callers. They can however be all {@link #shutdownNow() shut down} together, or replaced by a + * {@link #setFactory(Factory) change in Factory}. * - * @return default instance of a {@link Scheduler} that hosts a single-threaded + * @return the common single instance, a {@link Scheduler} that hosts a single-threaded * ExecutorService-based worker */ public static Scheduler single() { return cache(CACHED_SINGLE, SINGLE, SINGLE_SUPPLIER); } /** - * Wraps a single {@link reactor.core.scheduler.Scheduler.Worker} from some other - * {@link Scheduler} and provides {@link reactor.core.scheduler.Scheduler.Worker} + * Wraps a single {@link Scheduler.Worker} from some other + * {@link Scheduler} and provides {@link Scheduler.Worker} * services on top of it. Unlike with other factory methods in this class, the delegate - * is assumed to be {@link Scheduler#start() started} and won't be implicitly started - * by this method. + * is assumed to be {@link Scheduler#init() initialized} and won't be implicitly + * initialized by this method. *

                                * Use the {@link Scheduler#dispose()} to release the wrapped worker. * * @param original a {@link Scheduler} to call upon to get the single {@link - * reactor.core.scheduler.Scheduler.Worker} + * Scheduler.Worker} * * @return a wrapping {@link Scheduler} consistently returning a same worker from a * source {@link Scheduler} @@ -994,24 +969,6 @@ public interface Factory { /** - * {@link Scheduler} that dynamically creates Workers resources and caches - * eventually, reusing them once the Workers have been shut down. - *

                                - * The maximum number of created workers is unbounded. - * - * @param ttlSeconds Time-to-live for an idle {@link reactor.core.scheduler.Scheduler.Worker} - * @param threadFactory a {@link ThreadFactory} to use - * - * @return a new {@link Scheduler} that dynamically creates Workers resources and - * caches eventually, reusing them once the Workers have been shut down - * @deprecated use {@link Factory#newBoundedElastic(int, int, ThreadFactory, int)}, to be removed in 3.5.0 - */ - @Deprecated - default Scheduler newElastic(int ttlSeconds, ThreadFactory threadFactory) { - return new ElasticScheduler(threadFactory, ttlSeconds); - } - - /** * {@link Scheduler} that dynamically creates a bounded number of ExecutorService-based * Workers, reusing them once the Workers have been shut down. The underlying (user or daemon) * threads can be evicted if idle for more than {@code ttlSeconds}. @@ -1021,9 +978,9 @@ * @param threadCap maximum number of underlying threads to create * @param queuedTaskCap maximum number of tasks to enqueue when no more threads can be created. Can be {@link Integer#MAX_VALUE} for unbounded enqueueing. * @param threadFactory a {@link ThreadFactory} to use each thread initialization - * @param ttlSeconds Time-to-live for an idle {@link reactor.core.scheduler.Scheduler.Worker} + * @param ttlSeconds Time-to-live for an idle {@link Scheduler.Worker} * - * @return a new {@link Scheduler} that dynamically create workers with an upper bound to + * @return a new {@link Scheduler} that dynamically creates workers with an upper bound to * the number of backing threads, reuses threads and evict idle ones */ default Scheduler newBoundedElastic(int threadCap, int queuedTaskCap, ThreadFactory threadFactory, int ttlSeconds) { @@ -1065,9 +1022,6 @@ public static final class Snapshot implements Disposable { @Nullable - final CachedScheduler oldElasticScheduler; - - @Nullable final CachedScheduler oldBoundedElasticScheduler; @Nullable @@ -1078,12 +1032,10 @@ final Factory oldFactory; - private Snapshot(@Nullable CachedScheduler oldElasticScheduler, - @Nullable CachedScheduler oldBoundedElasticScheduler, + private Snapshot(@Nullable CachedScheduler oldBoundedElasticScheduler, @Nullable CachedScheduler oldParallelScheduler, @Nullable CachedScheduler oldSingleScheduler, Factory factory) { - this.oldElasticScheduler = oldElasticScheduler; this.oldBoundedElasticScheduler = oldBoundedElasticScheduler; this.oldParallelScheduler = oldParallelScheduler; this.oldSingleScheduler = oldSingleScheduler; @@ -1093,23 +1045,20 @@ @Override public boolean isDisposed() { return - (oldElasticScheduler == null || oldElasticScheduler.isDisposed()) && (oldBoundedElasticScheduler == null || oldBoundedElasticScheduler.isDisposed()) && (oldParallelScheduler == null || oldParallelScheduler.isDisposed()) && (oldSingleScheduler == null || oldSingleScheduler.isDisposed()); } @Override public void dispose() { - if (oldElasticScheduler != null) oldElasticScheduler._dispose(); if (oldBoundedElasticScheduler != null) oldBoundedElasticScheduler._dispose(); if (oldParallelScheduler != null) oldParallelScheduler._dispose(); if (oldSingleScheduler != null) oldSingleScheduler._dispose(); } } // Internals - static final String ELASTIC = "elastic"; // IO stuff static final String BOUNDED_ELASTIC = "boundedElastic"; // Blocking stuff with scale to zero static final String PARALLEL = "parallel"; //scale up common tasks static final String SINGLE = "single"; //non blocking tasks @@ -1119,14 +1068,10 @@ // Cached schedulers in atomic references: - static AtomicReference CACHED_ELASTIC = new AtomicReference<>(); static AtomicReference CACHED_BOUNDED_ELASTIC = new AtomicReference<>(); static AtomicReference CACHED_PARALLEL = new AtomicReference<>(); static AtomicReference CACHED_SINGLE = new AtomicReference<>(); - static final Supplier ELASTIC_SUPPLIER = - () -> newElastic(ELASTIC, ElasticScheduler.DEFAULT_TTL_SECONDS, true); - static final Supplier BOUNDED_ELASTIC_SUPPLIER = () -> newBoundedElastic(DEFAULT_BOUNDED_ELASTIC_SIZE, DEFAULT_BOUNDED_ELASTIC_QUEUESIZE, BOUNDED_ELASTIC, BoundedElasticScheduler.DEFAULT_TTL_SECONDS, true); @@ -1143,6 +1088,11 @@ static volatile Factory factory = DEFAULT; + private static final LinkedHashMap> onHandleErrorHooks = new LinkedHashMap<>(1); + + @Nullable + static BiConsumer onHandleErrorHook; + private static final LinkedHashMap> onScheduleHooks = new LinkedHashMap<>(1); @Nullable @@ -1242,6 +1192,11 @@ } @Override + public void init() { + cached.init(); + } + + @Override public void dispose() { } @@ -1456,5 +1411,4 @@ return null; } - -} +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/scheduler/SingleScheduler.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/core/scheduler/SingleScheduler.java (.../SingleScheduler.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/core/scheduler/SingleScheduler.java (.../SingleScheduler.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,34 +27,39 @@ import reactor.core.Disposable; import reactor.core.Scannable; +import reactor.core.publisher.Mono; /** * Scheduler that works with a single-threaded ScheduledExecutorService and is suited for * same-thread work (like an event dispatch thread). This scheduler is time-capable (can * schedule with delay / periodically). */ final class SingleScheduler implements Scheduler, Supplier, - Scannable { + Scannable, SchedulerState.DisposeAwaiter { static final AtomicLong COUNTER = new AtomicLong(); - - final ThreadFactory factory; - - volatile ScheduledExecutorService executor; - static final AtomicReferenceFieldUpdater EXECUTORS = - AtomicReferenceFieldUpdater.newUpdater(SingleScheduler.class, - ScheduledExecutorService.class, - "executor"); - static final ScheduledExecutorService TERMINATED; static { TERMINATED = Executors.newSingleThreadScheduledExecutor(); TERMINATED.shutdownNow(); } + final ThreadFactory factory; + + volatile SchedulerState state; + @SuppressWarnings("rawtypes") + private static final AtomicReferenceFieldUpdater STATE = + AtomicReferenceFieldUpdater.newUpdater( + SingleScheduler.class, SchedulerState.class, "state" + ); + + private static final SchedulerState INIT = + SchedulerState.init(TERMINATED); + SingleScheduler(ThreadFactory factory) { this.factory = factory; + STATE.lazySet(this, INIT); } /** @@ -71,59 +76,129 @@ @Override public boolean isDisposed() { - return executor == TERMINATED; + // we only consider disposed as actually shutdown + SchedulerState current = state; + return current != INIT && current.currentResource == TERMINATED; } @Override - public void start() { - //TODO SingleTimedScheduler didn't implement start, check if any particular reason? - ScheduledExecutorService b = null; - for (; ; ) { - ScheduledExecutorService a = executor; - if (a != TERMINATED && a != null) { - if (b != null) { - b.shutdownNow(); - } - return; + public void init() { + SchedulerState a = this.state; + if (a != INIT) { + if (a.currentResource == TERMINATED) { + throw new IllegalStateException( + "Initializing a disposed scheduler is not permitted" + ); } + // return early - scheduler already initialized + return; + } - if (b == null) { - b = Schedulers.decorateExecutorService(this, this.get()); - } + SchedulerState b = SchedulerState.init( + Schedulers.decorateExecutorService(this, this.get()) + ); - if (EXECUTORS.compareAndSet(this, a, b)) { - return; + if (!STATE.compareAndSet(this, INIT, b)) { + b.currentResource.shutdownNow(); + // Currently, isDisposed() is true for non-initialized state, but that will + // be fixed in 3.5.0. At this stage we know however that the state is no + // longer INIT, so isDisposed() actually means disposed state. + if (isDisposed()) { + throw new IllegalStateException( + "Initializing a disposed scheduler is not permitted" + ); } } } @Override + public void start() { + //TODO SingleTimedScheduler didn't implement start, check if any particular reason? + SchedulerState a = this.state; + if (a.currentResource != TERMINATED) { + return; + } + + SchedulerState b = SchedulerState.init( + Schedulers.decorateExecutorService(this, this.get()) + ); + + if (STATE.compareAndSet(this, a, b)) { + return; + } + + // someone else shutdown or started successfully, free the resource + b.currentResource.shutdownNow(); + } + + @Override + public boolean await(ScheduledExecutorService resource, long timeout, TimeUnit timeUnit) throws InterruptedException { + return resource.awaitTermination(timeout, timeUnit); + } + + @Override public void dispose() { - ScheduledExecutorService a = executor; - if (a != TERMINATED) { - a = EXECUTORS.getAndSet(this, TERMINATED); - if (a != TERMINATED && a != null) { - a.shutdownNow(); - } + SchedulerState previous = state; + + if (previous.currentResource == TERMINATED) { + // In case a graceful shutdown called shutdown and is waiting, but we want to force shutdown, + // we need access to the original ScheduledExecutorService. + assert previous.initialResource != null; + previous.initialResource.shutdownNow(); + return; } + + SchedulerState terminated = + SchedulerState.transition(previous.currentResource, TERMINATED, this); + + STATE.compareAndSet(this, previous, terminated); + + // If unsuccessful - either another thread disposed or restarted - no issue, + // we only care about the one stored in terminated. + assert terminated.initialResource != null; + terminated.initialResource.shutdownNow(); } @Override + public Mono disposeGracefully() { + return Mono.defer(() -> { + SchedulerState previous = state; + + if (previous.currentResource == TERMINATED) { + return previous.onDispose; + } + + SchedulerState terminated = + SchedulerState.transition(previous.currentResource, TERMINATED, this); + + STATE.compareAndSet(this, previous, terminated); + + // If unsuccessful - either another thread disposed or restarted - no issue, + // we only care about the one stored in terminated. + assert terminated.initialResource != null; + terminated.initialResource.shutdown(); + return terminated.onDispose; + }); + } + + @Override public Disposable schedule(Runnable task) { - return Schedulers.directSchedule(executor, task, null, 0L, TimeUnit.MILLISECONDS); + ScheduledExecutorService executor = state.currentResource; + return Schedulers.directSchedule(executor, task, null, 0L, + TimeUnit.MILLISECONDS); } @Override public Disposable schedule(Runnable task, long delay, TimeUnit unit) { - return Schedulers.directSchedule(executor, task, null, delay, unit); + return Schedulers.directSchedule(state.currentResource, task, null, delay, unit); } @Override public Disposable schedulePeriodically(Runnable task, long initialDelay, long period, TimeUnit unit) { - return Schedulers.directSchedulePeriodically(executor, + return Schedulers.directSchedulePeriodically(state.currentResource, task, initialDelay, period, @@ -146,12 +221,11 @@ if (key == Attr.NAME) return this.toString(); if (key == Attr.CAPACITY || key == Attr.BUFFERED) return 1; //BUFFERED: number of workers doesn't vary - return Schedulers.scanExecutor(executor, key); + return Schedulers.scanExecutor(state.currentResource, key); } @Override public Worker createWorker() { - return new ExecutorServiceWorker(executor); + return new ExecutorServiceWorker(state.currentResource); } - } Index: 3rdParty_sources/reactor/reactor/util/Loggers.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/util/Loggers.java (.../Loggers.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/util/Loggers.java (.../Loggers.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,10 @@ package reactor.util; import java.io.PrintStream; -import java.util.HashMap; +import java.lang.ref.WeakReference; +import java.util.Map; +import java.util.Objects; +import java.util.WeakHashMap; import java.util.function.Function; import java.util.logging.Level; import java.util.regex.Matcher; @@ -129,6 +132,12 @@ * The previously active logger factory is simply replaced without * any particular clean-up. * + *

                                Thread-safety

                                + * + * Given logger acquisition function must be thread-safe. + * It means that it is user responsibility to ensure that any internal state and cache + * used by the provided function is properly synchronized. + * * @param loggerFactory the {@link Function} that provides a (possibly cached) {@link Logger} * given a name. */ @@ -461,25 +470,27 @@ */ static final class ConsoleLogger implements Logger { - private final String name; + private final ConsoleLoggerKey identifier; private final PrintStream err; private final PrintStream log; - private final boolean verbose; - ConsoleLogger(String name, PrintStream log, PrintStream err, boolean verbose) { - this.name = name; + ConsoleLogger(ConsoleLoggerKey identifier, PrintStream log, PrintStream err) { + this.identifier = identifier; this.log = log; this.err = err; - this.verbose = verbose; } - ConsoleLogger(String name, boolean verbose) { - this(name, System.out, System.err, verbose); + ConsoleLogger(String name, PrintStream log, PrintStream err, boolean verbose) { + this(new ConsoleLoggerKey(name, verbose), log, err); } + ConsoleLogger(ConsoleLoggerKey identifier) { + this(identifier, System.out, System.err); + } + @Override public String getName() { - return this.name; + return identifier.name; } @Nullable @@ -498,27 +509,27 @@ @Override public boolean isTraceEnabled() { - return verbose; + return identifier.verbose; } @Override public synchronized void trace(String msg) { - if (!verbose) { + if (!identifier.verbose) { return; } this.log.format("[TRACE] (%s) %s\n", Thread.currentThread().getName(), msg); } @Override public synchronized void trace(String format, Object... arguments) { - if (!verbose) { + if (!identifier.verbose) { return; } this.log.format("[TRACE] (%s) %s\n", Thread.currentThread().getName(), format(format, arguments)); } @Override public synchronized void trace(String msg, Throwable t) { - if (!verbose) { + if (!identifier.verbose) { return; } this.log.format("[TRACE] (%s) %s - %s\n", Thread.currentThread().getName(), msg, t); @@ -527,28 +538,28 @@ @Override public boolean isDebugEnabled() { - return verbose; + return identifier.verbose; } @Override public synchronized void debug(String msg) { - if (!verbose) { + if (!identifier.verbose) { return; } this.log.format("[DEBUG] (%s) %s\n", Thread.currentThread().getName(), msg); } @Override public synchronized void debug(String format, Object... arguments) { - if (!verbose) { + if (!identifier.verbose) { return; } this.log.format("[DEBUG] (%s) %s\n", Thread.currentThread().getName(), format(format, arguments)); } @Override public synchronized void debug(String msg, Throwable t) { - if (!verbose) { + if (!identifier.verbose) { return; } this.log.format("[DEBUG] (%s) %s - %s\n", Thread.currentThread().getName(), msg, t); @@ -617,21 +628,76 @@ this.err.format("[ERROR] (%s) %s - %s\n", Thread.currentThread().getName(), msg, t); t.printStackTrace(this.err); } + + @Override + public String toString() { + return "ConsoleLogger[name="+getName()+", verbose="+identifier.verbose+"]"; + } } - private static final class ConsoleLoggerFactory implements Function { + /** + * A key object to serve a dual purpose: + *
                                  + *
                                • Allow consistent identification of cached console loggers using not + * only its name, but also its verbosity level
                                • + *
                                • Provide an object eligible to cache eviction. Contrary to a logger or + * a string (logger name) object, this is a good candidate for weak reference key, + * because it should be held only internally by the attached logger and by the + * logger cache (as evictable key).
                                • + *
                                + */ + private static final class ConsoleLoggerKey { - private static final HashMap consoleLoggers = new HashMap<>(); + private final String name; + private final boolean verbose; + private ConsoleLoggerKey(String name, boolean verbose) { + this.name = name; + this.verbose = verbose; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ConsoleLoggerKey key = (ConsoleLoggerKey) o; + return verbose == key.verbose && Objects.equals(name, key.name); + } + + @Override + public int hashCode() { + return Objects.hash(name, verbose); + } + } + + static final class ConsoleLoggerFactory implements Function { + + private static final Map> consoleLoggers = + new WeakHashMap<>(); + final boolean verbose; - private ConsoleLoggerFactory(boolean verbose) { + ConsoleLoggerFactory(boolean verbose) { this.verbose = verbose; } @Override public Logger apply(String name) { - return consoleLoggers.computeIfAbsent(name, n -> new ConsoleLogger(n, verbose)); + final ConsoleLoggerKey key = new ConsoleLoggerKey(name, verbose); + synchronized (consoleLoggers) { + final WeakReference ref = consoleLoggers.get(key); + Logger cached = ref == null ? null : ref.get(); + if (cached == null) { + cached = new ConsoleLogger(key); + consoleLoggers.put(key, new WeakReference<>(cached)); + } + + return cached; + } } } Index: 3rdParty_sources/reactor/reactor/util/Metrics.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/util/Metrics.java (.../Metrics.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/util/Metrics.java (.../Metrics.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,9 +23,12 @@ /** * Utilities around instrumentation and metrics with Micrometer. + * Deprecated as of 3.5.0, prefer using the new reactor-core-micrometer module. * * @author Simon Baslé + * @deprecated prefer using the new reactor-core-micrometer module Micrometer entrypoint. To be removed in 3.6.0 at the earliest. */ +@Deprecated public class Metrics { static final boolean isMicrometerAvailable; @@ -45,21 +48,36 @@ /** * Check if the current runtime supports metrics / instrumentation, by * verifying if Micrometer is on the classpath. + *

                                + * Note that this is regardless of whether the new reactor-core-micrometer module is also on the classpath (which + * could be the reason Micrometer is on the classpath in the first place). * * @return true if the Micrometer instrumentation facade is available + * @deprecated prefer explicit usage of the reactor-core-micrometer module. To be removed in 3.6.0 at the earliest. */ + @Deprecated public static final boolean isInstrumentationAvailable() { return isMicrometerAvailable; } + /** + * @deprecated Prefer using the reactor-core-micrometer module and configuring it using the Micrometer entrypoint. + */ + @Deprecated public static class MicrometerConfiguration { private static MeterRegistry registry = globalRegistry; /** * Set the registry to use in reactor for metrics related purposes. + *

                                + * This is only used by the deprecated inline Micrometer instrumentation, and not by the reactor-core-micrometer + * module. + * * @return the previously configured registry. + * @deprecated prefer using Micrometer setup in new reactor-core-micrometer module. To be removed at the earliest in 3.6.0. */ + @Deprecated public static MeterRegistry useRegistry(MeterRegistry registry) { MeterRegistry previous = MicrometerConfiguration.registry; MicrometerConfiguration.registry = registry; @@ -68,11 +86,17 @@ /** * Get the registry used in reactor for metrics related purposes. + *

                                + * This is only reflecting the deprecated inline Micrometer instrumentation configuration, and not the configuration + * of the reactor-core-micrometer module. + * + * @return the configured registry * @see Flux#metrics() + * @deprecated prefer using Micrometer setup in new reactor-core-micrometer module. To be removed at the earliest in 3.6.0. */ + @Deprecated public static MeterRegistry getRegistry() { - return MicrometerConfiguration.registry; + return registry; } } - } Index: 3rdParty_sources/reactor/reactor/util/concurrent/MpscLinkedQueue.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/util/concurrent/MpscLinkedQueue.java (.../MpscLinkedQueue.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/util/concurrent/MpscLinkedQueue.java (.../MpscLinkedQueue.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -63,7 +63,7 @@ * This works because each producer is guaranteed to 'plant' a new node and link the old node. No 2 * producers can get the same producer node as part of XCHG guarantee. * - * @see java.util.Queue#offer(java.lang.Object) + * @see java.util.Queue#offer(Object) */ @Override @SuppressWarnings("unchecked") @@ -92,7 +92,7 @@ * This works because each producer is guaranteed to 'plant' a new node and link the old node. No 2 * producers can get the same producer node as part of XCHG guarantee. * - * @see java.util.Queue#offer(java.lang.Object) + * @see java.util.Queue#offer(Object) * * @param e1 first element to offer * @param e2 second element to offer @@ -290,4 +290,4 @@ return next; } } -} +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/util/concurrent/SpscArrayQueue.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/util/concurrent/SpscArrayQueue.java (.../SpscArrayQueue.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/util/concurrent/SpscArrayQueue.java (.../SpscArrayQueue.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -175,13 +175,28 @@ mask = length - 1; } } +@SuppressWarnings("unused") class SpscArrayQueueP1 extends SpscArrayQueueCold { /** */ private static final long serialVersionUID = -4461305682174876914L; - - long p00, p01, p02, p03, p04, p05, p06, p07; - long p08, p09, p0A, p0B, p0C, p0D, p0E; + byte pad000,pad001,pad002,pad003,pad004,pad005,pad006,pad007;// 8b + byte pad010,pad011,pad012,pad013,pad014,pad015,pad016,pad017;// 16b + byte pad020,pad021,pad022,pad023,pad024,pad025,pad026,pad027;// 24b + byte pad030,pad031,pad032,pad033,pad034,pad035,pad036,pad037;// 32b + byte pad040,pad041,pad042,pad043,pad044,pad045,pad046,pad047;// 40b + byte pad050,pad051,pad052,pad053,pad054,pad055,pad056,pad057;// 48b + byte pad060,pad061,pad062,pad063,pad064,pad065,pad066,pad067;// 56b + byte pad070,pad071,pad072,pad073,pad074,pad075,pad076,pad077;// 64b + byte pad100,pad101,pad102,pad103,pad104,pad105,pad106,pad107;// 72b + byte pad110,pad111,pad112,pad113,pad114,pad115,pad116,pad117;// 80b + byte pad120,pad121,pad122,pad123,pad124,pad125,pad126,pad127;// 88b + byte pad130,pad131,pad132,pad133,pad134,pad135,pad136,pad137;// 96b + byte pad140,pad141,pad142,pad143,pad144,pad145,pad146,pad147;//104b + byte pad150,pad151,pad152,pad153,pad154,pad155,pad156,pad157;//112b + byte pad160,pad161,pad162,pad163,pad164,pad165,pad166,pad167;//120b + byte pad170,pad171,pad172,pad173,pad174,pad175,pad176,pad177;//128b + byte pad200,pad201,pad202,pad203; //132b SpscArrayQueueP1(int length) { super(length); } @@ -203,13 +218,28 @@ } +@SuppressWarnings("unused") class SpscArrayQueueP2 extends SpscArrayQueueProducer { /** */ private static final long serialVersionUID = -5400235061461013116L; - - long p00, p01, p02, p03, p04, p05, p06, p07; - long p08, p09, p0A, p0B, p0C, p0D, p0E; + byte pad000,pad001,pad002,pad003,pad004,pad005,pad006,pad007;// 8b + byte pad010,pad011,pad012,pad013,pad014,pad015,pad016,pad017;// 16b + byte pad020,pad021,pad022,pad023,pad024,pad025,pad026,pad027;// 24b + byte pad030,pad031,pad032,pad033,pad034,pad035,pad036,pad037;// 32b + byte pad040,pad041,pad042,pad043,pad044,pad045,pad046,pad047;// 40b + byte pad050,pad051,pad052,pad053,pad054,pad055,pad056,pad057;// 48b + byte pad060,pad061,pad062,pad063,pad064,pad065,pad066,pad067;// 56b + byte pad070,pad071,pad072,pad073,pad074,pad075,pad076,pad077;// 64b + byte pad100,pad101,pad102,pad103,pad104,pad105,pad106,pad107;// 72b + byte pad110,pad111,pad112,pad113,pad114,pad115,pad116,pad117;// 80b + byte pad120,pad121,pad122,pad123,pad124,pad125,pad126,pad127;// 88b + byte pad130,pad131,pad132,pad133,pad134,pad135,pad136,pad137;// 96b + byte pad140,pad141,pad142,pad143,pad144,pad145,pad146,pad147;//104b + byte pad150,pad151,pad152,pad153,pad154,pad155,pad156,pad157;//112b + byte pad160,pad161,pad162,pad163,pad164,pad165,pad166,pad167;//120b + byte pad170,pad171,pad172,pad173,pad174,pad175,pad176,pad177;//128b + SpscArrayQueueP2(int length) { super(length); } @@ -231,13 +261,28 @@ } +@SuppressWarnings("unused") class SpscArrayQueueP3 extends SpscArrayQueueConsumer { /** */ private static final long serialVersionUID = -2684922090021364171L; - - long p00, p01, p02, p03, p04, p05, p06, p07; - long p08, p09, p0A, p0B, p0C, p0D, p0E; + byte pad000,pad001,pad002,pad003,pad004,pad005,pad006,pad007;// 8b + byte pad010,pad011,pad012,pad013,pad014,pad015,pad016,pad017;// 16b + byte pad020,pad021,pad022,pad023,pad024,pad025,pad026,pad027;// 24b + byte pad030,pad031,pad032,pad033,pad034,pad035,pad036,pad037;// 32b + byte pad040,pad041,pad042,pad043,pad044,pad045,pad046,pad047;// 40b + byte pad050,pad051,pad052,pad053,pad054,pad055,pad056,pad057;// 48b + byte pad060,pad061,pad062,pad063,pad064,pad065,pad066,pad067;// 56b + byte pad070,pad071,pad072,pad073,pad074,pad075,pad076,pad077;// 64b + byte pad100,pad101,pad102,pad103,pad104,pad105,pad106,pad107;// 72b + byte pad110,pad111,pad112,pad113,pad114,pad115,pad116,pad117;// 80b + byte pad120,pad121,pad122,pad123,pad124,pad125,pad126,pad127;// 88b + byte pad130,pad131,pad132,pad133,pad134,pad135,pad136,pad137;// 96b + byte pad140,pad141,pad142,pad143,pad144,pad145,pad146,pad147;//104b + byte pad150,pad151,pad152,pad153,pad154,pad155,pad156,pad157;//112b + byte pad160,pad161,pad162,pad163,pad164,pad165,pad166,pad167;//120b + byte pad170,pad171,pad172,pad173,pad174,pad175,pad176,pad177;//128b + SpscArrayQueueP3(int length) { super(length); } Index: 3rdParty_sources/reactor/reactor/util/context/Context.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/util/context/Context.java (.../Context.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/util/context/Context.java (.../Context.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2017-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,10 +16,7 @@ package reactor.util.context; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.Objects; -import java.util.Optional; +import java.util.*; import reactor.util.annotation.Nullable; @@ -35,7 +32,7 @@ * might want to associate a dedicated mutable structure to a single key to represent his * own context instead of using multiple {@link #put}, which could be more costly. * Past five user key/value pair, the {@link Context} will use a copy-on-write - * implementation backed by a new {@link java.util.Map} on each {@link #put}. + * implementation backed by a new {@link Map} on each {@link #put}. * * @author Stephane Maldini */ @@ -284,6 +281,28 @@ } /** + * Create a new {@link Context} by merging the content of this context and a given + * {@link Map}. If the {@link Map} is empty, the same {@link Context} instance + * is returned. + * + * @param from the {@link Map} from which to include entries in the resulting {@link Context}. + * @return a new {@link Context} with a merge of the entries from this context and the given {@link Map}. + */ + default Context putAllMap(Map from) { + if (from.isEmpty()) { + return this; + } + + ContextN combined = new ContextN(this.size() + from.size()); + this.forEach(combined); + from.forEach(combined); + if (combined.size() <= 5) { + return Context.of((Map) combined); + } + return combined; + } + + /** * See {@link #putAll(ContextView)}. * * @deprecated will be removed in 3.5, kept for backward compatibility with 3.3. Until @@ -296,4 +315,4 @@ default Context putAll(Context context) { return this.putAll(context.readOnly()); } -} +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/util/context/Context0.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/util/context/Context0.java (.../Context0.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/util/context/Context0.java (.../Context0.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2015-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; +import java.util.function.BiConsumer; import java.util.stream.Stream; final class Context0 implements CoreContext { @@ -68,6 +69,10 @@ } @Override + public void forEach(BiConsumer action) { + } + + @Override public Context putAllInto(Context base) { return base; } Index: 3rdParty_sources/reactor/reactor/util/context/Context1.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/util/context/Context1.java (.../Context1.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/util/context/Context1.java (.../Context1.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2015-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; +import java.util.function.BiConsumer; import java.util.stream.Stream; final class Context1 implements CoreContext { @@ -73,6 +74,11 @@ } @Override + public void forEach(BiConsumer action) { + action.accept(key, value); + } + + @Override public Context putAllInto(Context base) { return base.put(key, value); } Index: 3rdParty_sources/reactor/reactor/util/context/Context2.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/util/context/Context2.java (.../Context2.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/util/context/Context2.java (.../Context2.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2015-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; +import java.util.function.BiConsumer; import java.util.stream.Stream; final class Context2 implements CoreContext { @@ -100,6 +101,12 @@ } @Override + public void forEach(BiConsumer action) { + action.accept(key1, value1); + action.accept(key2, value2); + } + + @Override public Context putAllInto(Context base) { return base .put(this.key1, this.value1) Index: 3rdParty_sources/reactor/reactor/util/context/Context3.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/util/context/Context3.java (.../Context3.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/util/context/Context3.java (.../Context3.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2015-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; +import java.util.function.BiConsumer; import java.util.stream.Stream; final class Context3 implements CoreContext { @@ -121,6 +122,13 @@ } @Override + public void forEach(BiConsumer action) { + action.accept(key1, value1); + action.accept(key2, value2); + action.accept(key3, value3); + } + + @Override public Context putAllInto(Context base) { return base .put(this.key1, this.value1) Index: 3rdParty_sources/reactor/reactor/util/context/Context4.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/util/context/Context4.java (.../Context4.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/util/context/Context4.java (.../Context4.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2017-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; +import java.util.function.BiConsumer; import java.util.stream.Stream; final class Context4 implements CoreContext { @@ -162,6 +163,14 @@ } @Override + public void forEach(BiConsumer action) { + action.accept(key1, value1); + action.accept(key2, value2); + action.accept(key3, value3); + action.accept(key4, value4); + } + + @Override public Context putAllInto(Context base) { return base .put(this.key1, this.value1) Index: 3rdParty_sources/reactor/reactor/util/context/Context5.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/util/context/Context5.java (.../Context5.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/util/context/Context5.java (.../Context5.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2017-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; +import java.util.function.BiConsumer; import java.util.stream.Stream; final class Context5 implements CoreContext { @@ -152,6 +153,15 @@ } @Override + public void forEach(BiConsumer action) { + action.accept(key1, value1); + action.accept(key2, value2); + action.accept(key3, value3); + action.accept(key4, value4); + action.accept(key5, value5); + } + + @Override public Context putAllInto(Context base) { return base .put(this.key1, this.value1) Index: 3rdParty_sources/reactor/reactor/util/context/ContextN.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/util/context/ContextN.java (.../ContextN.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/util/context/ContextN.java (.../ContextN.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2015-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -143,14 +143,14 @@ @Override public Stream> stream() { - return entrySet().stream().map(AbstractMap.SimpleImmutableEntry::new); + return entrySet().stream().map(SimpleImmutableEntry::new); } @Override public Context putAllInto(Context base) { - if (base instanceof ContextN) { + if (base instanceof CoreContext) { ContextN newContext = new ContextN(base.size() + this.size()); - newContext.putAll((Map) base); + ((CoreContext) base).unsafePutAllInto(newContext); newContext.putAll((Map) this); return newContext; } @@ -165,6 +165,20 @@ other.putAll((Map) this); } + /** + * This method is part of the {@link Map} API. As this is an internal + * implementation detail, no validation of the {@link Map} keys or values is + * performed. I.e. the caller must ensure they are not null, otherwise this + * {@link Context} will have disallowed mappings. + * Despite being public, this API is not exposed to end users and can be used + * internally for means of populating the inner contents of {@link ContextN}. + * + * @param m mappings to be stored in this map + */ + public void putAll(Map m) { + super.putAll(m); + } + @Override public Context putAll(ContextView other) { if (other.isEmpty()) return this; @@ -185,7 +199,18 @@ } @Override + public Context putAllMap(Map from) { + if (from.isEmpty()) { + return this; + } + + ContextN newContext = new ContextN(this); + from.forEach(newContext); + return newContext; + } + + @Override public String toString() { return "ContextN" + super.toString(); } -} +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/util/context/ContextView.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/util/context/ContextView.java (.../ContextView.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/util/context/ContextView.java (.../ContextView.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2020-2022 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import java.util.Map; import java.util.NoSuchElementException; import java.util.Optional; +import java.util.function.BiConsumer; import java.util.stream.Stream; import reactor.util.annotation.Nullable; @@ -134,4 +135,16 @@ * @return a {@link Stream} of key/value pairs held by this context */ Stream> stream(); + + /** + * Perform the given action for each entry in this {@link ContextView}. If the action throws an + * exception, it is immediately propagated to the caller and the remaining items + * will not be processed. + * + * @param action The action to be performed for each entry + * @throws NullPointerException if the specified action is null + */ + default void forEach(BiConsumer action) { + stream().forEach(entry -> action.accept(entry.getKey(), entry.getValue())); + } } Index: 3rdParty_sources/reactor/reactor/util/context/ReactorContextAccessor.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/util/context/ReactorContextAccessor.java (revision 0) +++ 3rdParty_sources/reactor/reactor/util/context/ReactorContextAccessor.java (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2022 VMware Inc. or its affiliates, All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reactor.util.context; + +import java.util.Map; +import java.util.function.Predicate; + +import io.micrometer.context.ContextAccessor; + +import reactor.util.annotation.Nullable; + +/** + * A {@code ContextAccessor} to enable reading values from a Reactor + * {@link ContextView} and writing values to {@link Context}. + *

                                + * Please note that this public class implements the {@code libs.micrometer.contextPropagation} + * SPI library, which is an optional dependency. + * + * @author Rossen Stoyanchev + * @author Simon Baslé + * @since 3.5.0 + */ +public final class ReactorContextAccessor implements ContextAccessor { + + @Override + public Class readableType() { + return ContextView.class; + } + + @Override + public void readValues(ContextView source, Predicate keyPredicate, Map target) { + source.forEach((k, v) -> { + if (keyPredicate.test(k)) { + target.put(k, v); + } + }); + } + + @Override + @Nullable + public T readValue(ContextView sourceContext, Object key) { + return sourceContext.getOrDefault(key, null); + } + + @Override + public Class writeableType() { + return Context.class; + } + + @Override + public Context writeValues(Map source, Context target) { + return target.putAllMap(source); + } +} Fisheye: Tag c4ce08dc0aae7d9da822088a3d5710484f6b0402 refers to a dead (removed) revision in file `3rdParty_sources/reactor/reactor/util/function/TupleExtensions.kt'. Fisheye: No comparison available. Pass `N' to diff? Index: 3rdParty_sources/reactor/reactor/util/package-info.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/util/package-info.java (.../package-info.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/util/package-info.java (.../package-info.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -20,5 +20,4 @@ @NonNullApi package reactor.util; -import reactor.util.annotation.NonNullApi; -import javax.annotation.Nullable; \ No newline at end of file +import reactor.util.annotation.NonNullApi; \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/util/retry/ImmutableRetrySignal.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/util/retry/ImmutableRetrySignal.java (.../ImmutableRetrySignal.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/util/retry/ImmutableRetrySignal.java (.../ImmutableRetrySignal.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -20,7 +20,7 @@ import reactor.util.context.ContextView; /** - * An immutable {@link reactor.util.retry.Retry.RetrySignal} that can be used for retained + * An immutable {@link Retry.RetrySignal} that can be used for retained * copies of mutable implementations. * * @author Simon Baslé @@ -74,4 +74,4 @@ public String toString() { return "attempt #" + (failureTotalIndex + 1) + " (" + (failureSubsequentIndex + 1) + " in a row), last failure={" + failure + '}'; } -} +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/util/retry/RetryBackoffSpec.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/util/retry/RetryBackoffSpec.java (.../RetryBackoffSpec.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/util/retry/RetryBackoffSpec.java (.../RetryBackoffSpec.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2020-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,6 +31,7 @@ import reactor.core.scheduler.Scheduler; import reactor.core.scheduler.Schedulers; import reactor.util.annotation.Nullable; +import reactor.util.context.Context; import reactor.util.context.ContextView; /** @@ -47,8 +48,8 @@ * Only errors that match the {@link #filter(Predicate)} are retried (by default all), * and the number of attempts can also limited with {@link #maxAttempts(long)}. * When the maximum attempt of retries is reached, a runtime exception is propagated downstream which - * can be pinpointed with {@link reactor.core.Exceptions#isRetryExhausted(Throwable)}. The cause of - * the last attempt's failure is attached as said {@link reactor.core.Exceptions#retryExhausted(String, Throwable) retryExhausted} + * can be pinpointed with {@link Exceptions#isRetryExhausted(Throwable)}. The cause of + * the last attempt's failure is attached as said {@link Exceptions#retryExhausted(String, Throwable) retryExhausted} * exception's cause. This can be customized with {@link #onRetryExhaustedThrow(BiFunction)}. *

                                * Additionally, to help dealing with bursts of transient errors in a long-lived Flux as if each burst @@ -371,14 +372,14 @@ * Set the generator for the {@link Exception} to be propagated when the maximum amount of retries * is exhausted. By default, throws an {@link Exceptions#retryExhausted(String, Throwable)} with the * message reflecting the total attempt index, transient attempt index and maximum retry count. - * The cause of the last {@link reactor.util.retry.Retry.RetrySignal} is also added + * The cause of the last {@link RetrySignal} is also added * as the exception's cause. * * * @param retryExhaustedGenerator the {@link Function} that generates the {@link Throwable} for the last - * {@link reactor.util.retry.Retry.RetrySignal} + * {@link RetrySignal} * @return a new copy of the {@link RetryBackoffSpec} which can either be further - * configured or used as {@link reactor.util.retry.Retry} + * configured or used as {@link Retry} */ public RetryBackoffSpec onRetryExhaustedThrow(BiFunction retryExhaustedGenerator) { return new RetryBackoffSpec( @@ -399,8 +400,8 @@ /** * Set the transient error mode, indicating that the strategy being built should use - * {@link reactor.util.retry.Retry.RetrySignal#totalRetriesInARow()} rather than - * {@link reactor.util.retry.Retry.RetrySignal#totalRetries()}. + * {@link RetrySignal#totalRetriesInARow()} rather than + * {@link RetrySignal#totalRetries()}. * Transient errors are errors that could occur in bursts but are then recovered from by * a retry (with one or more onNext signals) before another error occurs. *

                                @@ -539,69 +540,72 @@ @Override public Flux generateCompanion(Flux t) { validateArguments(); - return t.concatMap(retryWhenState -> { - //capture the state immediately - RetrySignal copy = retryWhenState.copy(); - Throwable currentFailure = copy.failure(); - long iteration = isTransientErrors ? copy.totalRetriesInARow() : copy.totalRetries(); + return Flux.deferContextual(cv -> + t.contextWrite(cv) + .concatMap(retryWhenState -> { + //capture the state immediately + RetrySignal copy = retryWhenState.copy(); + Throwable currentFailure = copy.failure(); + long iteration = isTransientErrors ? copy.totalRetriesInARow() : copy.totalRetries(); - if (currentFailure == null) { - return Mono.error(new IllegalStateException("Retry.RetrySignal#failure() not expected to be null")); - } + if (currentFailure == null) { + return Mono.error(new IllegalStateException("Retry.RetrySignal#failure() not expected to be null")); + } - if (!errorFilter.test(currentFailure)) { - return Mono.error(currentFailure); - } + if (!errorFilter.test(currentFailure)) { + return Mono.error(currentFailure); + } - if (iteration >= maxAttempts) { - return Mono.error(retryExhaustedGenerator.apply(this, copy)); - } + if (iteration >= maxAttempts) { + return Mono.error(retryExhaustedGenerator.apply(this, copy)); + } - Duration nextBackoff; - try { - nextBackoff = minBackoff.multipliedBy((long) Math.pow(2, iteration)); - if (nextBackoff.compareTo(maxBackoff) > 0) { + Duration nextBackoff; + try { + nextBackoff = minBackoff.multipliedBy((long) Math.pow(2, iteration)); + if (nextBackoff.compareTo(maxBackoff) > 0) { + nextBackoff = maxBackoff; + } + } + catch (ArithmeticException overflow) { nextBackoff = maxBackoff; } - } - catch (ArithmeticException overflow) { - nextBackoff = maxBackoff; - } - //short-circuit delay == 0 case - if (nextBackoff.isZero()) { - return RetrySpec.applyHooks(copy, Mono.just(iteration), - syncPreRetry, syncPostRetry, asyncPreRetry, asyncPostRetry); - } + //short-circuit delay == 0 case + if (nextBackoff.isZero()) { + return RetrySpec.applyHooks(copy, Mono.just(iteration), + syncPreRetry, syncPostRetry, asyncPreRetry, asyncPostRetry, cv); + } - ThreadLocalRandom random = ThreadLocalRandom.current(); + ThreadLocalRandom random = ThreadLocalRandom.current(); - long jitterOffset; - try { - jitterOffset = nextBackoff.multipliedBy((long) (100 * jitterFactor)) - .dividedBy(100) - .toMillis(); - } - catch (ArithmeticException ae) { - jitterOffset = Math.round(Long.MAX_VALUE * jitterFactor); - } - long lowBound = Math.max(minBackoff.minus(nextBackoff) - .toMillis(), -jitterOffset); - long highBound = Math.min(maxBackoff.minus(nextBackoff) - .toMillis(), jitterOffset); + long jitterOffset; + try { + jitterOffset = nextBackoff.multipliedBy((long) (100 * jitterFactor)) + .dividedBy(100) + .toMillis(); + } + catch (ArithmeticException ae) { + jitterOffset = Math.round(Long.MAX_VALUE * jitterFactor); + } + long lowBound = Math.max(minBackoff.minus(nextBackoff) + .toMillis(), -jitterOffset); + long highBound = Math.min(maxBackoff.minus(nextBackoff) + .toMillis(), jitterOffset); - long jitter; - if (highBound == lowBound) { - if (highBound == 0) jitter = 0; - else jitter = random.nextLong(highBound); - } - else { - jitter = random.nextLong(lowBound, highBound); - } - Duration effectiveBackoff = nextBackoff.plusMillis(jitter); - return RetrySpec.applyHooks(copy, Mono.delay(effectiveBackoff, - backoffSchedulerSupplier.get()), - syncPreRetry, syncPostRetry, asyncPreRetry, asyncPostRetry); - }); + long jitter; + if (highBound == lowBound) { + if (highBound == 0) jitter = 0; + else jitter = random.nextLong(highBound); + } + else { + jitter = random.nextLong(lowBound, highBound); + } + Duration effectiveBackoff = nextBackoff.plusMillis(jitter); + return RetrySpec.applyHooks(copy, Mono.delay(effectiveBackoff, backoffSchedulerSupplier.get()), + syncPreRetry, syncPostRetry, asyncPreRetry, asyncPostRetry, cv); + }) + .onErrorStop() + ); } -} +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/util/retry/RetrySpec.java =================================================================== diff -u -r03ee7b3a8cdecd210e54619377d06f9c7cb4014b -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/reactor/reactor/util/retry/RetrySpec.java (.../RetrySpec.java) (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) +++ 3rdParty_sources/reactor/reactor/util/retry/RetrySpec.java (.../RetrySpec.java) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2020-2023 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,7 @@ import reactor.core.Exceptions; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import reactor.util.context.Context; import reactor.util.context.ContextView; /** @@ -35,8 +36,8 @@ * Only errors that match the {@link #filter(Predicate)} are retried (by default all), up to {@link #maxAttempts(long)} times. *

                                * When the maximum attempt of retries is reached, a runtime exception is propagated downstream which - * can be pinpointed with {@link reactor.core.Exceptions#isRetryExhausted(Throwable)}. The cause of - * the last attempt's failure is attached as said {@link reactor.core.Exceptions#retryExhausted(String, Throwable) retryExhausted} + * can be pinpointed with {@link Exceptions#isRetryExhausted(Throwable)}. The cause of + * the last attempt's failure is attached as said {@link Exceptions#retryExhausted(String, Throwable) retryExhausted} * exception's cause. This can be customized with {@link #onRetryExhaustedThrow(BiFunction)}. *

                                * Additionally, to help dealing with bursts of transient errors in a long-lived Flux as if each burst @@ -302,10 +303,10 @@ * Set the generator for the {@link Exception} to be propagated when the maximum amount of retries * is exhausted. By default, throws an {@link Exceptions#retryExhausted(String, Throwable)} with the * message reflecting the total attempt index, transient attempt index and maximum retry count. - * The cause of the last {@link reactor.util.retry.Retry.RetrySignal} is also added as the exception's cause. + * The cause of the last {@link RetrySignal} is also added as the exception's cause. * * @param retryExhaustedGenerator the {@link Function} that generates the {@link Throwable} for the last - * {@link reactor.util.retry.Retry.RetrySignal} + * {@link RetrySignal} * @return a new copy of the {@link RetrySpec} which can either be further configured or used as {@link Retry} */ public RetrySpec onRetryExhaustedThrow(BiFunction retryExhaustedGenerator) { @@ -323,8 +324,8 @@ /** * Set the transient error mode, indicating that the strategy being built should use - * {@link reactor.util.retry.Retry.RetrySignal#totalRetriesInARow()} rather than - * {@link reactor.util.retry.Retry.RetrySignal#totalRetries()}. + * {@link RetrySignal#totalRetriesInARow()} rather than + * {@link RetrySignal#totalRetries()}. * Transient errors are errors that could occur in bursts but are then recovered from by * a retry (with one or more onNext signals) before another error occurs. *

                                @@ -353,25 +354,30 @@ @Override public Flux generateCompanion(Flux flux) { - return flux.concatMap(retryWhenState -> { - //capture the state immediately - RetrySignal copy = retryWhenState.copy(); - Throwable currentFailure = copy.failure(); - long iteration = isTransientErrors ? copy.totalRetriesInARow() : copy.totalRetries(); + return Flux.deferContextual(cv -> + flux + .contextWrite(cv) + .concatMap(retryWhenState -> { + //capture the state immediately + RetrySignal copy = retryWhenState.copy(); + Throwable currentFailure = copy.failure(); + long iteration = isTransientErrors ? copy.totalRetriesInARow() : copy.totalRetries(); - if (currentFailure == null) { - return Mono.error(new IllegalStateException("RetryWhenState#failure() not expected to be null")); - } - else if (!errorFilter.test(currentFailure)) { - return Mono.error(currentFailure); - } - else if (iteration >= maxAttempts) { - return Mono.error(retryExhaustedGenerator.apply(this, copy)); - } - else { - return applyHooks(copy, Mono.just(iteration), doPreRetry, doPostRetry, asyncPreRetry, asyncPostRetry); - } - }); + if (currentFailure == null) { + return Mono.error(new IllegalStateException("RetryWhenState#failure() not expected to be null")); + } + else if (!errorFilter.test(currentFailure)) { + return Mono.error(currentFailure); + } + else if (iteration >= maxAttempts) { + return Mono.error(retryExhaustedGenerator.apply(this, copy)); + } + else { + return applyHooks(copy, Mono.just(iteration), doPreRetry, doPostRetry, asyncPreRetry, asyncPostRetry, cv); + } + }) + .onErrorStop() + ); } //=================== @@ -383,7 +389,8 @@ final Consumer doPreRetry, final Consumer doPostRetry, final BiFunction, Mono> asyncPreRetry, - final BiFunction, Mono> asyncPostRetry) { + final BiFunction, Mono> asyncPostRetry, + final ContextView cv) { if (doPreRetry != NO_OP_CONSUMER) { try { doPreRetry.accept(copyOfSignal); @@ -404,6 +411,6 @@ Mono preRetryMono = asyncPreRetry == NO_OP_BIFUNCTION ? Mono.empty() : asyncPreRetry.apply(copyOfSignal, Mono.empty()); Mono postRetryMono = asyncPostRetry != NO_OP_BIFUNCTION ? asyncPostRetry.apply(copyOfSignal, postRetrySyncMono) : postRetrySyncMono; - return preRetryMono.then(originalCompanion).flatMap(postRetryMono::thenReturn); + return preRetryMono.then(originalCompanion).flatMap(postRetryMono::thenReturn).contextWrite(cv); } -} +} \ No newline at end of file Index: 3rdParty_sources/versions.txt =================================================================== diff -u -rf5d27dea6e5752fec029c67db67a5b7df49e10e8 -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- 3rdParty_sources/versions.txt (.../versions.txt) (revision f5d27dea6e5752fec029c67db67a5b7df49e10e8) +++ 3rdParty_sources/versions.txt (.../versions.txt) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -60,8 +60,10 @@ picketbox 5.0.3 -Reactive Streams 1.0.3 +Reactive Streams 1.0.4 +Reactor 3.5.11 + Servlet API 4.0.0 Spring 5.3.18 Index: idea_project/.idea/libraries/3rdParty.xml =================================================================== diff -u -rbaf2503b7b75aa6c2d59b3df2e3a16f62a3d80d5 -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- idea_project/.idea/libraries/3rdParty.xml (.../3rdParty.xml) (revision baf2503b7b75aa6c2d59b3df2e3a16f62a3d80d5) +++ idea_project/.idea/libraries/3rdParty.xml (.../3rdParty.xml) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -26,7 +26,6 @@ - @@ -49,8 +48,9 @@ - + + Index: lams_build/3rdParty.userlibraries =================================================================== diff -u -rf5d27dea6e5752fec029c67db67a5b7df49e10e8 -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- lams_build/3rdParty.userlibraries (.../3rdParty.userlibraries) (revision f5d27dea6e5752fec029c67db67a5b7df49e10e8) +++ lams_build/3rdParty.userlibraries (.../3rdParty.userlibraries) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -26,7 +26,7 @@ - + @@ -48,7 +48,7 @@ - + \ No newline at end of file Index: lams_build/lib/reactivestreams/reactive-streams-1.0.3.jar =================================================================== diff -u -r4bcdd9565f14ec38ad5b21fb982196f4c1746528 -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 Binary files differ Index: lams_build/lib/reactivestreams/reactive-streams-1.0.4.jar =================================================================== diff -u Binary files differ Index: lams_build/lib/reactivestreams/reactivestreams.module.xml =================================================================== diff -u -r4bcdd9565f14ec38ad5b21fb982196f4c1746528 -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- lams_build/lib/reactivestreams/reactivestreams.module.xml (.../reactivestreams.module.xml) (revision 4bcdd9565f14ec38ad5b21fb982196f4c1746528) +++ lams_build/lib/reactivestreams/reactivestreams.module.xml (.../reactivestreams.module.xml) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -29,6 +29,6 @@ - + \ No newline at end of file Index: lams_build/lib/reactor/reactor-core-3.4.12.jar =================================================================== diff -u -r50f28fabcac6dcd46979f8da77041931065f632d -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 Binary files differ Index: lams_build/lib/reactor/reactor-core-3.5.11.jar =================================================================== diff -u Binary files differ Index: lams_build/lib/reactor/reactor.module.xml =================================================================== diff -u -refec391166809c65ec52314045d6c3142dded9f1 -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- lams_build/lib/reactor/reactor.module.xml (.../reactor.module.xml) (revision efec391166809c65ec52314045d6c3142dded9f1) +++ lams_build/lib/reactor/reactor.module.xml (.../reactor.module.xml) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -24,10 +24,10 @@ - + - + \ No newline at end of file Index: lams_build/liblist.txt =================================================================== diff -u -r924d4186e55122ce91b5dcaba3311ab026060e94 -rc4ce08dc0aae7d9da822088a3d5710484f6b0402 --- lams_build/liblist.txt (.../liblist.txt) (revision 924d4186e55122ce91b5dcaba3311ab026060e94) +++ lams_build/liblist.txt (.../liblist.txt) (revision c4ce08dc0aae7d9da822088a3d5710484f6b0402) @@ -64,9 +64,9 @@ passpol passpol-0.7.1-SNAPSHOT.jar 0.7.1 Apache License 2.0 Codahale For checking for weak or compromised password. Recompiled manually with Java 11. -reactive-streams reactive-streams-1.0.3.jar 1.0.3 Public Domain Reactive StreamsVMWare Reactive protocol +reactive-streams reactive-streams-1.0.4.jar 1.0.4 Public Domain Reactive StreamsVMWare Reactive protocol -reactor reactor-core-3.4.12.jar 3.4.12 Apache License 2.0 VMWare Reactive backend +reactor reactor-core-3.5.11.jar 3.5.11 Apache License 2.0 VMWare Reactive backend quartz quartz-2.2.3.jar 2.2.3 Apache License 2.0 Terracotta For running scheduled jobs